'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')); } // --- CORE DYNAMIC SUBFOLDER RESOLUTION ENGINE --- $allowed_core_types = array( 'wordpress', 'core', 'default', 'admin', 'admin-network', 'continents-cities', 'network', 'continents', 'cities' ); $is_core = in_array( $type, $allowed_core_types, true ); $sub_folder = $is_core ? 'core' : ( ( '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); } // Differentiate main core from distinct admin/network sub-projects to map proper filenames $is_main_core = ( $is_core && ( empty( $component ) || 'default' === $component || 'core' === $component || 'wordpress' === $component ) ); $po_filename = $is_main_core ? "{$selected_locale}.po" : "{$component}-{$selected_locale}.po"; $sandbox_po_target = trailingslashit($sandbox_dir) . $po_filename; $sandbox_mo_target = str_replace('.po', '.mo', $sandbox_po_target); // --- BACKWARD-COMPATIBILITY MIGRATION ENGINE (INITIALIZATION LAYER) --- // Prevents overwriting valid lowercase files on case-sensitive Linux platforms during dashboard routing if ( ! file_exists( $sandbox_po_target ) ) { $lowercase_locale = strtolower( $selected_locale ); $lowercase_filename = $is_main_core ? "{$lowercase_locale}.po" : "{$component}-{$lowercase_locale}.po"; $lowercase_po_target = trailingslashit($sandbox_dir) . $lowercase_filename; if ( file_exists( $lowercase_po_target ) ) { @rename( $lowercase_po_target, $sandbox_po_target ); $lowercase_mo_target = str_replace('.po', '.mo', $lowercase_po_target); if ( file_exists( $lowercase_mo_target ) ) { @rename( $lowercase_mo_target, $sandbox_mo_target ); } } } $log_context = ! empty( $component ) ? $component : $type; // 5. CASE 1: Sandbox file already exists - Prioritize and load immediately if (!$force_blank && file_exists($sandbox_po_target)) { // TRACKING ACTION: Log opening an already existing sandboxed translation catalog if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { $log_message = sprintf( /* translators: 1: target language code uppercase, 2: target file name asset */ __( 'Opened existing translation workspace for language "%1$s" (%2$s).', 'af-translate' ), strtoupper( $selected_locale ), $po_filename ); AF_Translate_Admin::log_activity( $log_message, $log_context ); } 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 source structures $bundle_candidates = array(); if ( $is_core ) { // Map global core translation files out of default system repository directories $bundle_candidates[] = trailingslashit( WP_LANG_DIR ) . $po_filename; } else { // FIX: Case-sensitive absolute path resolution for Linux if ( 'plugin' === $type ) { $base_dir = trailingslashit( WP_PLUGIN_DIR ) . $component; } else { $theme_obj = wp_get_theme( $component ); $base_dir = $theme_obj->exists() ? $theme_obj->get_stylesheet_directory() : 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); } // TRACKING ACTION: Log migrating existing bundle files over to the secure sandbox if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { $log_message = sprintf( /* translators: 1: target language code uppercase, 2: target file name asset */ __( 'Migrated pre-existing baseline bundle dataset into workspace for language "%1$s" (%2$s).', 'af-translate' ), strtoupper( $selected_locale ), $po_filename ); AF_Translate_Admin::log_activity( $log_message, $log_context ); } 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_file_name = $is_main_core ? 'wordpress.pot' : "{$component}.pot"; if ( $is_core ) { $pot_candidates = array( $sandbox_dir . '/' . $pot_file_name, trailingslashit( WP_LANG_DIR ) . $pot_file_name ); } else { $pot_candidates = array( $sandbox_dir . '/' . $pot_file_name, $base_dir . "/languages/" . $pot_file_name, $base_dir . "/" . $pot_file_name ); } 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 lacks a POT, extract/generate it now if (empty($pot_file)) { $sandbox_pot_fallback = $sandbox_dir . '/' . $pot_file_name; $generated = false; if (class_exists('AF_Translate_POT_Generator')) { $generated = AF_Translate_POT_Generator::generate_master_pot($component, $type, $sandbox_pot_fallback); } // Fallback Resiliency Engine for Core: Read existing localized entries to format clean blank templates if ( !$generated && $is_core ) { $po_file_source = ''; foreach ( $bundle_candidates as $cand ) { if ( file_exists( $cand ) ) { $po_file_source = $cand; break; } } if ( ! empty( $po_file_source ) && file_exists( $po_file_source ) && class_exists( 'AF_Translate_PO_Processor' ) ) { $parsed_entries = AF_Translate_PO_Processor::parse_file( $po_file_source ); if ( is_array( $parsed_entries ) ) { foreach ( $parsed_entries as &$entry ) { if ( isset( $entry['msgstr'] ) ) { $entry['msgstr'] = is_array( $entry['msgstr'] ) ? array_fill( 0, count( $entry['msgstr'] ), '' ) : ''; } } unset( $entry ); $pot_content = AF_Translate_PO_Processor::generate_po_string( $parsed_entries ); if ( ! empty( $pot_content ) && false !== file_put_contents( $sandbox_pot_fallback, $pot_content ) ) { $generated = true; } } } } if ( $generated && 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); // TRACKING ACTION: Log fresh initialization from template file rules if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { $log_message = sprintf( /* translators: 1: target language code uppercase, 2: target file name asset */ __( 'Initiated clean translation workspace from template for language "%1$s" (%2$s).', 'af-translate' ), strtoupper( $selected_locale ), $po_filename ); AF_Translate_Admin::log_activity( $log_message, $log_context ); } 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'; // --- CASE-PRESERVATION RETRIEVAL ENGINE --- // CRITICAL FIX: Replaced sanitize_key() with alphanumeric filtering to preserve regional uppercase rules (e.g., lt_LT) on Linux environments $locale = isset($_POST['target_locale']) ? preg_replace('/[^a-zA-Z0-9_-]/', '', 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')); } // --- CORE DYNAMIC SUBFOLDER RESOLUTION ENGINE --- $allowed_core_types = array( 'wordpress', 'core', 'default', 'admin', 'admin-network', 'continents-cities', 'network', 'continents', 'cities' ); $is_core = in_array( $type, $allowed_core_types, true ); $sub_folder = $is_core ? 'core' : ( ( '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); } $is_main_core = ( $is_core && ( empty( $component ) || 'default' === $component || 'core' === $component || 'wordpress' === $component ) ); $po_filename = $is_main_core ? "{$locale}.po" : "{$component}-{$locale}.po"; $po_target = trailingslashit($target_dir) . $po_filename; $mo_target = str_replace('.po', '.mo', $po_target); // --- BACKWARD-COMPATIBILITY MIGRATION ENGINE (IMPORT LAYER) --- // Normalizes lowercase file targets inside sandbox to prevent duplicate scanning bypass loops on Linux if ( ! file_exists( $po_target ) ) { $lowercase_locale = strtolower( $locale ); $lowercase_filename = $is_main_core ? "{$lowercase_locale}.po" : "{$component}-{$lowercase_locale}.po"; $lowercase_po_target = trailingslashit($target_dir) . $lowercase_filename; if ( file_exists( $lowercase_po_target ) ) { @rename( $lowercase_po_target, $po_target ); $lowercase_mo_target = str_replace('.po', '.mo', $lowercase_po_target); if ( file_exists( $lowercase_mo_target ) ) { @rename( $lowercase_mo_target, $mo_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 our sandboxed uploads path if (file_exists($po_target)) { $file_exists_anywhere = true; } // Check B: Look inside default vendor bundle frameworks if (!$file_exists_anywhere) { if ( $is_core ) { $bundle_candidates = array( trailingslashit( WP_LANG_DIR ) . $po_filename ); } else { // FIX: Case-sensitive absolute path resolution for Linux if ( 'plugin' === $type ) { $base_dir = trailingslashit( WP_PLUGIN_DIR ) . $component; } else { $theme_obj = wp_get_theme( $component ); $base_dir = $theme_obj->exists() ? $theme_obj->get_stylesheet_directory() : 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 to prevent incrementing structural filename issues (-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); } // TRACKING ACTION: Log direct localized .po manual dashboard uploads $log_context = ! empty( $component ) ? $component : $type; if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { $log_message = sprintf( /* translators: 1: target language code uppercase, 2: processed target base filename string */ __( 'Imported external localization mapping dataset for language "%1$s" (%2$s).', 'af-translate' ), strtoupper( $locale ), $po_filename ); AF_Translate_Admin::log_activity( $log_message, $log_context ); } 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')); } // --- CORE DYNAMIC SUBFOLDER RESOLUTION ENGINE --- $allowed_core_types = array( 'wordpress', 'core', 'default', 'admin', 'admin-network', 'continents-cities', 'network', 'continents', 'cities' ); $is_core = in_array( $type, $allowed_core_types, true ); $sub_folder = $is_core ? 'core' : ( ( 'plugin' === $type ) ? 'plugins' : 'themes' ); if ( $is_core ) { $source_candidates = array( trailingslashit( WP_LANG_DIR ) . $filename ); } else { // Case-sensitive absolute path resolution for Linux environments if ( 'plugin' === $type ) { $base_dir = trailingslashit( WP_PLUGIN_DIR ) . $component; } else { $theme_obj = wp_get_theme( $component ); $base_dir = $theme_obj->exists() ? $theme_obj->get_stylesheet_directory() : 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; } } // Clean locale extraction based on main core vs. prefixed workflows $is_main_core = ( $is_core && ( empty( $component ) || 'default' === $component || 'core' === $component || 'wordpress' === $component ) ); $pure_name = basename($filename, '.po'); $locale = $is_main_core ? $pure_name : str_replace($component . '-', '', $pure_name); // Standardize alignment normalization layer $locale = preg_replace( '/[^a-zA-Z0-9_-]/', '', $locale ); $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')); } $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) . ( $is_main_core ? "{$locale}.po" : "{$component}-{$locale}.po" ); // --- BACKWARD-COMPATIBILITY MIGRATION ENGINE (SYNC LAYER) --- // Catch historical lowercase sandbox entries and silently upgrade their filenames if ( ! file_exists( $po_file ) ) { $lowercase_locale = strtolower( $locale ); $lowercase_po_file = trailingslashit($target_path) . ( $is_main_core ? "{$lowercase_locale}.po" : "{$component}-{$lowercase_locale}.po" ); if ( file_exists( $lowercase_po_file ) ) { @rename( $lowercase_po_file, $po_file ); $lowercase_mo_file = str_replace( '.po', '.mo', $lowercase_po_file ); $mo_file = str_replace( '.po', '.mo', $po_file ); if ( file_exists( $lowercase_mo_file ) ) { @rename( $lowercase_mo_file, $mo_file ); } } } 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')); } // --- MASTER POT ROUTING LAYERS --- $pot_file = ''; $pot_file_name = $is_main_core ? 'wordpress.pot' : "{$component}.pot"; if ( $is_core ) { $pot_candidates = array( $target_path . '/' . $pot_file_name, trailingslashit( WP_LANG_DIR ) . $pot_file_name ); } else { $pot_candidates = array( $target_path . '/' . $pot_file_name, $base_dir . '/languages/' . $pot_file_name, $base_dir . '/' . $pot_file_name ); 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( $pot_candidates, $root_pots ); } } } 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; } } } $sandbox_sync_pot = $target_path . '/' . $pot_file_name; if ( empty( $pot_file ) ) { $generated = false; if ( class_exists( 'AF_Translate_POT_Generator' ) ) { $generated = AF_Translate_POT_Generator::generate_master_pot( $component, $type, $sandbox_sync_pot ); } if ( !$generated && !empty( $fallback_po ) && file_exists( $fallback_po ) ) { if ( class_exists( 'AF_Translate_PO_Processor' ) ) { $parsed_entries = AF_Translate_PO_Processor::parse_file( $fallback_po ); if ( is_array( $parsed_entries ) ) { foreach ( $parsed_entries as &$entry ) { if ( isset( $entry['msgstr'] ) ) { $entry['msgstr'] = is_array( $entry['msgstr'] ) ? array_fill( 0, count( $entry['msgstr'] ), '' ) : ''; } } unset( $entry ); $pot_content = AF_Translate_PO_Processor::generate_po_string( $parsed_entries ); if ( ! empty( $pot_content ) && false !== file_put_contents( $sandbox_sync_pot, $pot_content ) ) { $generated = true; } } } } if ( $generated && file_exists( $sandbox_sync_pot ) ) { $pot_file = $sandbox_sync_pot; } } if ( empty( $pot_file ) && ! empty( $fallback_po ) && file_exists( $fallback_po ) ) { if ( class_exists( 'AF_Translate_PO_Processor' ) ) { $parsed_entries = AF_Translate_PO_Processor::parse_file( $fallback_po ); if ( is_array( $parsed_entries ) ) { foreach ( $parsed_entries as &$entry ) { if ( isset( $entry['msgstr'] ) ) { $entry['msgstr'] = is_array( $entry['msgstr'] ) ? array_fill( 0, count( $entry['msgstr'] ), '' ) : ''; } } unset( $entry ); $pot_content = AF_Translate_PO_Processor::generate_po_string( $parsed_entries ); if ( ! empty( $pot_content ) && false !== file_put_contents( $sandbox_sync_pot, $pot_content ) ) { $pot_file = $sandbox_sync_pot; } } } } 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')); } // --- NATIVE METADATA AND SCHEMA ENRICHMENT LAYER --- if ( class_exists( 'AF_Translate_PO_Processor' ) ) { $pot_entries = AF_Translate_PO_Processor::parse_file( $pot_file ); if ( is_array( $pot_entries ) ) { $pot_entries_map = array(); foreach ( $pot_entries as $entry ) { if ( isset( $entry['msgid'] ) ) { $pot_entries_map[ $entry['msgid'] ] = true; } } $has_updates = false; // Scenario A: Enriched Theme Parsing Matrix if ( 'theme' === $type ) { $theme_obj = wp_get_theme( $component ); if ( $theme_obj->exists() ) { $theme_headers = array( 'Theme Name' => array( 'val' => $theme_obj->get('Name'), 'comment' => 'Theme Name of the theme' ), 'Theme URI' => array( 'val' => $theme_obj->get('ThemeURI'), 'comment' => 'Theme URI of the theme' ), 'Description' => array( 'val' => $theme_obj->get('Description'), 'comment' => 'Description of the theme' ), 'Author' => array( 'val' => $theme_obj->get('Author'), 'comment' => 'Author of the theme' ), 'Author URI' => array( 'val' => $theme_obj->get('AuthorURI'), 'comment' => 'Author URI of the theme' ), ); foreach ( $theme_headers as $t_data ) { if ( ! empty( $t_data['val'] ) && ! isset( $pot_entries_map[ $t_data['val'] ] ) ) { $pot_entries[] = array( 'msgctxt' => '', 'msgid' => $t_data['val'], 'msgid_plural' => '', 'msgstr' => array(''), 'comments' => '#. ' . $t_data['comment'] ); $pot_entries_map[ $t_data['val'] ] = true; $has_updates = true; } } } // Modern Full Site Editing (FSE) / theme.json Extraction Matrix $theme_json_file = isset( $base_dir ) ? trailingslashit( $base_dir ) . 'theme.json' : ''; if ( ! empty( $theme_json_file ) && file_exists( $theme_json_file ) ) { $json_raw = @file_get_contents( $theme_json_file ); $json_data = json_decode( $json_raw, true ); if ( is_array( $json_data ) ) { $extracted_json_strings = array(); if ( isset( $json_data['customTemplates'] ) && is_array( $json_data['customTemplates'] ) ) { foreach ( $json_data['customTemplates'] as $tpl ) { if ( ! empty( $tpl['title'] ) ) { $extracted_json_strings[] = array( 'val' => $tpl['title'], 'comment' => 'Template Title from theme.json' ); } } } if ( isset( $json_data['templateParts'] ) && is_array( $json_data['templateParts'] ) ) { foreach ( $json_data['templateParts'] as $part ) { if ( ! empty( $part['title'] ) ) { $extracted_json_strings[] = array( 'val' => $part['title'], 'comment' => 'Template Part Title from theme.json' ); } } } if ( isset( $json_data['settings'] ) && is_array( $json_data['settings'] ) ) { if ( isset( $json_data['settings']['color']['palette'] ) && is_array( $json_data['settings']['color']['palette'] ) ) { foreach ( $json_data['settings']['color']['palette'] as $item ) { if ( ! empty( $item['name'] ) ) { $extracted_json_strings[] = array( 'val' => $item['name'], 'comment' => 'Color Name from theme.json' ); } } } if ( isset( $json_data['settings']['color']['gradients'] ) && is_array( $json_data['settings']['color']['gradients'] ) ) { foreach ( $json_data['settings']['color']['gradients'] as $item ) { if ( ! empty( $item['name'] ) ) { $extracted_json_strings[] = array( 'val' => $item['name'], 'comment' => 'Gradient Name from theme.json' ); } } } if ( isset( $json_data['settings']['typography']['fontSizes'] ) && is_array( $json_data['settings']['typography']['fontSizes'] ) ) { foreach ( $json_data['settings']['typography']['fontSizes'] as $item ) { if ( ! empty( $item['name'] ) ) { $extracted_json_strings[] = array( 'val' => $item['name'], 'comment' => 'Font Size Name from theme.json' ); } } } if ( isset( $json_data['settings']['typography']['fontFamilies'] ) && is_array( $json_data['settings']['typography']['fontFamilies'] ) ) { foreach ( $json_data['settings']['typography']['fontFamilies'] as $item ) { if ( ! empty( $item['name'] ) ) { $extracted_json_strings[] = array( 'val' => $item['name'], 'comment' => 'Font Family Name from theme.json' ); } } } } foreach ( $extracted_json_strings as $js_str ) { if ( ! empty( $js_str['val'] ) && ! isset( $pot_entries_map[ $js_str['val'] ] ) ) { $pot_entries[] = array( 'msgctxt' => '', 'msgid' => $js_str['val'], 'msgid_plural' => '', 'msgstr' => array(''), 'comments' => '#. ' . $js_str['comment'] ); $pot_entries_map[ $js_str['val'] ] = true; $has_updates = true; } } } } // Scenario B: Enriched Plugin Manifest Parsing Matrix } elseif ( 'plugin' === $type && isset( $base_dir ) && is_dir( $base_dir ) ) { if ( ! function_exists( 'get_plugin_data' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugin_files = glob( trailingslashit( $base_dir ) . '*.php' ); if ( ! empty( $plugin_files ) && is_array( $plugin_files ) ) { foreach ( $plugin_files as $file ) { $plugin_data = get_plugin_data( $file, false, false ); if ( ! empty( $plugin_data['Name'] ) ) { $plugin_headers = array( 'Plugin Name' => array( 'val' => $plugin_data['Name'], 'comment' => 'Plugin Name of the plugin' ), 'Plugin URI' => array( 'val' => $plugin_data['PluginURI'], 'comment' => 'Plugin URI of the plugin' ), 'Description' => array( 'val' => $plugin_data['Description'], 'comment' => 'Description of the plugin' ), 'Author' => array( 'val' => $plugin_data['Author'], 'comment' => 'Author of the plugin' ), 'Author URI' => array( 'val' => $plugin_data['AuthorURI'], 'comment' => 'Author URI of the plugin' ), ); foreach ( $plugin_headers as $p_data ) { if ( ! empty( $p_data['val'] ) && ! isset( $pot_entries_map[ $p_data['val'] ] ) ) { $pot_entries[] = array( 'msgctxt' => '', 'msgid' => $p_data['val'], 'msgid_plural' => '', 'msgstr' => array(''), 'comments' => '#. ' . $p_data['comment'] ); $pot_entries_map[ $p_data['val'] ] = true; $has_updates = true; } } break; } } } } // Safely update the isolated template container directly inside the sandbox if ( $has_updates ) { $enriched_pot_content = AF_Translate_PO_Processor::generate_po_string( $pot_entries ); if ( ! empty( $enriched_pot_content ) ) { if ( false !== file_put_contents( $sandbox_sync_pot, $enriched_pot_content ) ) { $pot_file = $sandbox_sync_pot; } } } } } $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); } $log_context = ! empty( $component ) ? $component : $type; if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { $log_message = sprintf( __( 'Synchronized translation workspace catalog with current template assets for language "%1$s" (%2$s). Added: %3$d, Dropped: %4$d.', 'af-translate' ), strtoupper( $locale ), $filename, intval( $sync_results['new'] ), intval( $sync_results['obsolete'] ) ); AF_Translate_Admin::log_activity( $log_message, $log_context ); } $message = sprintf( 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')); } }