'af-translate', 'view' => $component, 'type' => $type, 'action' => 'edit', 'lang' => $selected_locale ), admin_url('admin.php') ); // 4. Setup Safe Sandbox Paths $wp_upload_info = wp_upload_dir(); if (!empty($wp_upload_info['error'])) { wp_send_json_error(esc_html__('Uploads folder configuration runtime mismatch.', 'af-translate')); } $sub_folder = ('plugin' === $type) ? 'plugins' : 'themes'; $sandbox_dir = trailingslashit($wp_upload_info['basedir']) . 'af-translate/' . $sub_folder; if (!is_dir($sandbox_dir)) { wp_mkdir_p($sandbox_dir); } $sandbox_po_target = trailingslashit($sandbox_dir) . "{$component}-{$selected_locale}.po"; $sandbox_mo_target = str_replace('.po', '.mo', $sandbox_po_target); // 5. CASE 1: Sandbox file already exists - Prioritize and load immediately if (!$force_blank && file_exists($sandbox_po_target)) { wp_send_json_success(array( 'message' => esc_html__('Existing sandbox translation file detected. Loading editor...', 'af-translate'), 'redirect' => esc_url_raw($redirect_url) )); } // 6. CASE 2: Bootstrap & Migrate progress from parent theme/plugin bundles $base_dir = ('plugin' === $type) ? trailingslashit(WP_PLUGIN_DIR) . $component : trailingslashit(get_theme_root()) . $component; $bundle_candidates = array( $base_dir . "/languages/{$component}-{$selected_locale}.po", $base_dir . "/languages/{$selected_locale}.po", $base_dir . "/language/{$component}-{$selected_locale}.po", $base_dir . "/language/{$selected_locale}.po", $base_dir . "/{$component}-{$selected_locale}.po", $base_dir . "/{$selected_locale}.po", ); $found_source_po = ''; foreach ($bundle_candidates as $candidate) { $canonical_candidate = AF_Translate_Path_Validator::resolve_canonical_path($candidate); if (file_exists($canonical_candidate)) { $found_source_po = $canonical_candidate; break; } } if (!$force_blank && !empty($found_source_po)) { if (AF_Translate_Path_Validator::validate_containment($found_source_po, false) && AF_Translate_Path_Validator::validate_containment($sandbox_po_target, true)) { if (@copy($found_source_po, $sandbox_po_target)) { $found_source_mo = str_replace('.po', '.mo', $found_source_po); if (file_exists($found_source_mo) && AF_Translate_Path_Validator::validate_containment($found_source_mo, false)) { @copy($found_source_mo, $sandbox_mo_target); } else { AF_Translate_Compiler_Engine::compile_po_to_mo($sandbox_po_target, $sandbox_mo_target); } wp_send_json_success(array( 'message' => esc_html__('Pre-existing translation discovered. Safely migrated progress to sandbox!', 'af-translate'), 'redirect' => esc_url_raw($redirect_url) )); } } } // 7. CASE 3: Clean template initialization from .pot reference maps $pot_file = ''; $pot_candidates = array( $sandbox_dir . "/{$component}.pot", // Added: Look for custom sandboxed POT template first $base_dir . "/languages/{$component}.pot", $base_dir . "/{$component}.pot" ); foreach ($pot_candidates as $candidate) { $canonical_pot = AF_Translate_Path_Validator::resolve_canonical_path($candidate); if (file_exists($canonical_pot)) { $pot_file = $canonical_pot; break; } } // ON-THE-FLY GENERATION INTERCEPTOR: If extension has no POT file, generate it into the sandbox now! if (empty($pot_file)) { $sandbox_pot_fallback = $sandbox_dir . "/{$component}.pot"; if (class_exists('AF_Translate_POT_Generator')) { if (AF_Translate_POT_Generator::generate_master_pot($component, $type, $sandbox_pot_fallback)) { if (file_exists($sandbox_pot_fallback)) { $pot_file = $sandbox_pot_fallback; } } } } if (! empty($pot_file) && file_exists($pot_file)) { if (AF_Translate_Path_Validator::validate_containment($pot_file, false) && AF_Translate_Path_Validator::validate_containment($sandbox_po_target, true)) { $pot_contents = file_get_contents($pot_file); if (false !== $pot_contents) { $pot_contents = preg_replace('/"Language: .*?\\\n"/', "\"Language: {$selected_locale}\\n\"", $pot_contents); if (false !== file_put_contents($sandbox_po_target, $pot_contents)) { AF_Translate_Compiler_Engine::compile_po_to_mo($sandbox_po_target, $sandbox_mo_target); wp_send_json_success(array( 'message' => esc_html__('Blank translation template initiated successfully.', 'af-translate'), 'redirect' => esc_url_raw($redirect_url) )); } } } } wp_send_json_error(esc_html__('Could not locate or generate a master template asset (.pot) to create a translation file.', 'af-translate')); } /** * Processes external local file uploads inside wp-content/uploads/af-translate/ * Safeguarded with duplicate scanning parameters and user confirmation routing. */ public static function ajax_import_po_handler() { check_ajax_referer('af_translate_secure_action', 'security'); if (!current_user_can('manage_options')) { wp_send_json_error(esc_html__('Privilege validation runtime failure context.', 'af-translate'), 403); } $component = isset($_POST['component']) ? sanitize_key(wp_unslash($_POST['component'])) : ''; $type = isset($_POST['type']) ? sanitize_key(wp_unslash($_POST['type'])) : 'theme'; $locale = isset($_POST['target_locale']) ? sanitize_key(wp_unslash($_POST['target_locale'])) : ''; $overwrite = isset($_POST['overwrite']) ? true : false; if (empty($component) || empty($locale)) { wp_send_json_error(esc_html__('Target components or locale variables missing execution context.', 'af-translate')); } if (!isset($_FILES['po_file_upload']) || !is_array($_FILES['po_file_upload']) || empty($_FILES['po_file_upload']['name'])) { wp_send_json_error(esc_html__('File payload extraction mismatch or empty streaming buffer encountered.', 'af-translate')); } if (!isset($_FILES['po_file_upload']['error']) || $_FILES['po_file_upload']['error'] !== UPLOAD_ERR_OK) { wp_send_json_error(esc_html__('File upload error occurred during transmission.', 'af-translate')); } $uploaded_name = sanitize_text_field(wp_unslash($_FILES['po_file_upload']['name'])); $uploaded_ext = strtolower(pathinfo($uploaded_name, PATHINFO_EXTENSION)); if ('po' !== $uploaded_ext) { wp_send_json_error(esc_html__('Invalid file format configuration context. Only legitimate .po files are permitted.', 'af-translate')); } $wp_upload_info = wp_upload_dir(); if (!empty($wp_upload_info['error'])) { wp_send_json_error(esc_html__('Unable to determine the site uploads directory.', 'af-translate')); } $sub_folder = ('plugin' === $type) ? 'plugins' : 'themes'; $target_dir = trailingslashit($wp_upload_info['basedir']) . 'af-translate/' . $sub_folder; if (!is_dir($target_dir)) { wp_mkdir_p($target_dir); } $po_filename = "{$component}-{$locale}.po"; $po_target = trailingslashit($target_dir) . $po_filename; $mo_target = str_replace('.po', '.mo', $po_target); if (!AF_Translate_Path_Validator::validate_containment($po_target, true)) { wp_send_json_error(esc_html__('Target routing location constraint safety violation flag.', 'af-translate')); } // --- DUPLICATE SANITY CHECK LAYER --- if (!$overwrite) { $file_exists_anywhere = false; // Check A: Look inside the custom upload sandbox directory if (file_exists($po_target)) { $file_exists_anywhere = true; } // Check B: Look inside core vendor extension directories if (!$file_exists_anywhere) { $base_dir = ('plugin' === $type) ? trailingslashit(WP_PLUGIN_DIR) . $component : trailingslashit(get_theme_root()) . $component; $bundle_candidates = array( $base_dir . "/languages/{$component}-{$locale}.po", $base_dir . "/languages/{$locale}.po", $base_dir . "/language/{$component}-{$locale}.po", $base_dir . "/language/{$locale}.po", $base_dir . "/{$component}-{$locale}.po", $base_dir . "/{$locale}.po", ); foreach ($bundle_candidates as $candidate) { $canonical_candidate = AF_Translate_Path_Validator::resolve_canonical_path($candidate); if (file_exists($canonical_candidate)) { $file_exists_anywhere = true; break; } } } if ($file_exists_anywhere) { wp_send_json_error(array( 'code' => 'duplicate_detected', 'message' => esc_html__('A localization dataset file already exists for this language configuration. Would you like to overwrite it inside your secure sandbox space?', 'af-translate'), )); } } else { // --- CLEAR INTERFERENCE FOR OVERWRITE --- // If overwrite is true, delete existing target files in the sandbox directory first. // This stops wp_handle_upload() from appending incrementing numerical suffixes like -1.po if (file_exists($po_target)) { wp_delete_file($po_target); } if (file_exists($mo_target)) { wp_delete_file($mo_target); } } $_FILES['po_file_upload']['name'] = $po_filename; $custom_upload_dir_callback = function ($uploads) use ($target_dir) { $uploads['path'] = $target_dir; $uploads['url'] = $target_dir; $uploads['subdir'] = ''; $uploads['basedir'] = $target_dir; $uploads['baseurl'] = $target_dir; return $uploads; }; add_filter('upload_dir', $custom_upload_dir_callback); if (!function_exists('wp_handle_upload')) { require_once ABSPATH . 'wp-admin/includes/file'; } $upload_overrides = array('test_form' => false, 'test_type' => false); $move_file = wp_handle_upload($_FILES['po_file_upload'], $upload_overrides); remove_filter('upload_dir', $custom_upload_dir_callback); if (!empty($move_file['error'])) { /* translators: 1: error details/string */ wp_send_json_error(sprintf(esc_html__('WordPress file uploader failure error: %1$s', 'af-translate'), $move_file['error'])); } if (!empty($move_file['file'])) { if (AF_Translate_Path_Validator::validate_containment($mo_target, true)) { AF_Translate_Compiler_Engine::compile_po_to_mo($move_file['file'], $mo_target); } wp_send_json_success(esc_html__('Dataset array configuration context mapping completely imported.', 'af-translate')); } wp_send_json_error(esc_html__('Internal processing error executing localization compilation workflows.', 'af-translate')); } /** * Executes template synchronization inside wp-content/uploads/af-translate/ */ public static function ajax_sync_po_handler() { check_ajax_referer('af_translate_secure_action', 'security'); if (!current_user_can('manage_options')) { wp_send_json_error(esc_html__('Security clearance configuration runtime error.', 'af-translate'), 403); } $filename = isset($_POST['filename']) ? sanitize_text_field(wp_unslash($_POST['filename'])) : ''; $component = isset($_POST['component']) ? sanitize_key(wp_unslash($_POST['component'])) : ''; $type = isset($_POST['type']) ? sanitize_key(wp_unslash($_POST['type'])) : 'theme'; if (empty($filename) || empty($component)) { wp_send_json_error(esc_html__('Missing matching execution rules or parameter values.', 'af-translate')); } $base_dir = ('plugin' === $type) ? trailingslashit(WP_PLUGIN_DIR) . $component : trailingslashit(get_theme_root()) . $component; $source_candidates = array( $base_dir . '/languages/' . $filename, $base_dir . '/language/' . $filename, $base_dir . '/' . $filename ); $fallback_po = ''; foreach ($source_candidates as $candidate) { $canonical_candidate = AF_Translate_Path_Validator::resolve_canonical_path($candidate); if (file_exists($canonical_candidate)) { $fallback_po = $canonical_candidate; break; } } $pure_name = basename($filename, '.po'); $locale = str_replace($component . '-', '', $pure_name); $wp_upload_info = wp_upload_dir(); if (!empty($wp_upload_info['error'])) { wp_send_json_error(esc_html__('Unable to resolve safe uploads folder architecture configuration.', 'af-translate')); } $sub_folder = ('plugin' === $type) ? 'plugins' : 'themes'; $target_path = trailingslashit($wp_upload_info['basedir']) . 'af-translate/' . $sub_folder; if (!is_dir($target_path)) { wp_mkdir_p($target_path); } $po_file = trailingslashit($target_path) . $component . '-' . $locale . '.po'; if (!file_exists($po_file) && !empty($fallback_po)) { if (AF_Translate_Path_Validator::validate_containment($fallback_po, false)) { @copy($fallback_po, $po_file); } } if (!AF_Translate_Path_Validator::validate_containment($po_file, true)) { wp_send_json_error(esc_html__('Path constraint containment breach flag raised during synchronization.', 'af-translate')); } $pot_file = ''; $pot_candidates = array( $target_path . '/' . $component . '.pot', // Added: Look inside your safe uploads sandbox folder first $base_dir . '/languages/' . $component . '.pot', $base_dir . '/' . $component . '.pot' ); if ( is_dir( $base_dir . '/languages' ) ) { $lang_pots = glob( trailingslashit( $base_dir . '/languages' ) . '*.pot' ); if ( ! empty( $lang_pots ) && is_array( $lang_pots ) ) { $pot_candidates = array_merge( $pot_candidates, $lang_pots ); } } if ( is_dir( $base_dir ) ) { $root_pots = glob( trailingslashit( $base_dir ) . '*.pot' ); if ( ! empty( $root_pots ) && is_array( $root_pots ) ) { $pot_candidates = array_merge( $root_pots, $pot_candidates ); } } foreach ( $pot_candidates as $candidate ) { if ( is_string( $candidate ) ) { $canonical_pot = AF_Translate_Path_Validator::resolve_canonical_path( $candidate ); if ( file_exists( $canonical_pot ) ) { $pot_file = $canonical_pot; break; } } } // ON-THE-FLY SYNCHRONIZATION GENERATOR: Ensure missing file templates are dynamically built right now if ( empty( $pot_file ) ) { $sandbox_sync_pot = $target_path . '/' . $component . '.pot'; if ( class_exists( 'AF_Translate_POT_Generator' ) ) { if ( AF_Translate_POT_Generator::generate_master_pot( $component, $type, $sandbox_sync_pot ) ) { if ( file_exists( $sandbox_sync_pot ) ) { $pot_file = $sandbox_sync_pot; } } } } if ( empty( $pot_file ) && ! empty( $fallback_po ) ) { $pot_file = $fallback_po; } if ( empty( $pot_file ) || ! file_exists( $pot_file ) ) { wp_send_json_error( esc_html__( 'Resource mapping tracking template file could not be discovered or generated.', 'af-translate' ) ); } if (!AF_Translate_Path_Validator::validate_containment($pot_file, false)) { wp_send_json_error(esc_html__('Master reference template containment path security break.', 'af-translate')); } $sync_results = AF_Translate_Compiler_Engine::sync_pot_into_po($po_file, $pot_file); if (false !== $sync_results) { $mo_file = str_replace('.po', '.mo', $po_file); if (AF_Translate_Path_Validator::validate_containment($mo_file, true)) { AF_Translate_Compiler_Engine::compile_po_to_mo($po_file, $mo_file); } $message = sprintf( /* translators: 1: number of new strings found, 2: number of obsolete items dropped */ esc_html__('Sync complete! %1$d new strings found, %2$d obsolete items dropped.', 'af-translate'), intval($sync_results['new']), intval($sync_results['obsolete']) ); wp_send_json_success($message); } wp_send_json_error(esc_html__('Internal file compilation merge error encountered.', 'af-translate')); } }