fr_FR) if ( false !== strpos( $custom_input, '_' ) ) { $parts = explode( '_', $custom_input ); if ( 2 === count( $parts ) ) { $custom_input = strtolower( $parts[0] ) . '_' . strtoupper( $parts[1] ); } } else { $custom_input = strtolower( $custom_input ); } if ( empty( $custom_input ) || ! preg_match( '/^[a-z]{2,3}(_[A-Z]{2,4})?$/', $custom_input ) ) { wp_send_json_error( esc_html__( 'Invalid target custom locale string pattern detected.', 'af-translate' ) ); } $selected_locale = $custom_input; } if ( empty( $component ) || empty( $selected_locale ) || ! preg_match( '/^[a-z]{2,3}(_[A-Z]{2,4})?(_[a-zA-Z0-9_]+)?$/', $selected_locale ) ) { wp_send_json_error( esc_html__( 'Critical Operational Error: Selected locale parameter schema mismatch.', 'af-translate' ) ); } $redirect_url = add_query_arg( array( 'page' => 'af-translate', 'view' => $component, 'type' => $type, 'action' => 'edit', 'lang' => $selected_locale ), admin_url( 'admin.php' ) ); $meta = AF_Translate_Path_Resolver::resolve_workspace_meta( $component, $type, $selected_locale ); // Ensure paths are normalized $target_dir = wp_normalize_path( $meta['target_dir'] ); $po_target = wp_normalize_path( $meta['po_target'] ); $meta['target_dir'] = $target_dir; $meta['po_target'] = $po_target; // Force directory creation and write-permission verification if ( ! is_dir( $target_dir ) ) { wp_mkdir_p( $target_dir ); } if ( ! wp_is_writable( $target_dir ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod @chmod( $target_dir, 0755 ); } // Add index.php guard if ( is_dir( $target_dir ) && ! file_exists( $target_dir . '/index.php' ) ) { @file_put_contents( $target_dir . '/index.php', ' esc_html__( 'Existing sandbox translation file detected. Loading editor...', 'af-translate' ), 'redirect' => esc_url_raw( $redirect_url ) ) ); } // CASE 2: Bootstrap & Migrate progress from template bundles $bundle_candidates = AF_Translate_Path_Resolver::get_bundle_candidates( $meta, $component, $selected_locale ); $found_source_po = ''; foreach ( $bundle_candidates as $candidate ) { $canonical_candidate = wp_normalize_path( 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 ( @copy( $found_source_po, $po_target ) ) { AF_Translate_Artifact_Compiler::compile_all( $po_target, $meta ); if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { /* translators: 1: Target locale code, 2: Target PO file name */ $log_message = sprintf( __( 'Migrated pre-existing baseline bundle dataset into workspace for language "%1$s" (%2$s).', 'af-translate' ), strtoupper( $selected_locale ), $meta['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 ) ) ); } } // CASE 3: Clean template initialization from .pot reference maps $pot_file = ''; $pot_file_name = $meta['is_main_core'] ? 'wordpress.pot' : "{$component}.pot"; $base_dir = wp_normalize_path( $meta['base_dir'] ); $pot_candidates = $meta['is_core'] ? array( $target_dir . '/' . $pot_file_name, wp_normalize_path( trailingslashit( WP_LANG_DIR ) ) . $pot_file_name ) : array( $target_dir . '/' . $pot_file_name, $base_dir . '/languages/' . $pot_file_name, $base_dir . '/language/' . $pot_file_name, $base_dir . '/' . $pot_file_name ); // Candidate loop foreach ( $pot_candidates as $candidate ) { $norm_cand = wp_normalize_path( $candidate ); if ( file_exists( $norm_cand ) ) { $pot_file = $norm_cand; break; } } // Glob fallback if exact filename didn't match $component.pot if ( empty( $pot_file ) && ! $meta['is_core'] && ! empty( $base_dir ) ) { $lang_dir = $base_dir . '/languages'; if ( is_dir( $lang_dir ) ) { $glob_pots = glob( $lang_dir . '/*.pot' ); if ( ! empty( $glob_pots ) && is_array( $glob_pots ) ) { $pot_file = wp_normalize_path( $glob_pots[0] ); } } } // Dynamic generator fallback if ( empty( $pot_file ) ) { $sandbox_pot_fallback = $target_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 ); } if ( $generated && file_exists( $sandbox_pot_fallback ) ) { $pot_file = wp_normalize_path( $sandbox_pot_fallback ); } } // Write file using located POT template if ( ! empty( $pot_file ) && file_exists( $pot_file ) ) { $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( $po_target, $pot_contents ) ) { AF_Translate_Artifact_Compiler::compile_all( $po_target, $meta ); if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { /* translators: 1: Target locale code, 2: Target PO file name */ $log_message = sprintf( __( 'Initiated clean translation workspace from template for language "%1$s" (%2$s).', 'af-translate' ), strtoupper( $selected_locale ), $meta['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 ) ) ); } } } // CASE 4: Zero-Template Fallback Header (Guarantees workspace creation) $fallback_header = "msgid \"\"\n" . "msgstr \"\"\n" . "\"Project-Id-Version: " . esc_attr( $component ) . "\\n\"\n" . "\"Report-Msgid-Bugs-To: \\n\"\n" . "\"POT-Creation-Date: " . current_time( 'mysql' ) . "\\n\"\n" . "\"PO-Revision-Date: " . current_time( 'mysql' ) . "\\n\"\n" . "\"Last-Translator: \\n\"\n" . "\"Language-Team: \\n\"\n" . "\"Language: " . esc_attr( $selected_locale ) . "\\n\"\n" . "\"MIME-Version: 1.0\\n\"\n" . "\"Content-Type: text/plain; charset=UTF-8\\n\"\n" . "\"Content-Transfer-Encoding: 8bit\\n\"\n" . "\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\n"; if ( false !== file_put_contents( $po_target, $fallback_header ) ) { AF_Translate_Artifact_Compiler::compile_all( $po_target, $meta ); if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { /* translators: 1: Target locale code, 2: Target PO file name */ $log_message = sprintf( __( 'Initialized blank header translation file for language "%1$s" (%2$s).', 'af-translate' ), strtoupper( $selected_locale ), $meta['po_filename'] ); AF_Translate_Admin::log_activity( $log_message, $log_context ); } wp_send_json_success( array( 'message' => esc_html__( 'Blank translation workspace initialized successfully.', 'af-translate' ), 'redirect' => esc_url_raw( $redirect_url ) ) ); } wp_send_json_error( esc_html__( 'Critical system failure: Unable to write initialization file to disk target.', 'af-translate' ) ); } /** * Processes external local file uploads inside wp-content/uploads/af-translate/ */ public static function process_import() { 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; $has_file = isset( $_FILES['po_file_upload'] ) && is_array( $_FILES['po_file_upload'] ); $file_error = $has_file && isset( $_FILES['po_file_upload']['error'] ) ? absint( $_FILES['po_file_upload']['error'] ) : UPLOAD_ERR_NO_FILE; $file_name = $has_file && isset( $_FILES['po_file_upload']['name'] ) ? sanitize_file_name( wp_unslash( $_FILES['po_file_upload']['name'] ) ) : ''; if ( empty( $component ) || empty( $locale ) || UPLOAD_ERR_OK !== $file_error ) { wp_send_json_error( esc_html__( 'Target structural metrics or upload file buffer data blocks missing.', 'af-translate' ) ); } if ( 'po' !== strtolower( pathinfo( $file_name, PATHINFO_EXTENSION ) ) ) { wp_send_json_error( esc_html__( 'Invalid file format configuration context. Only legitimate .po files are permitted.', 'af-translate' ) ); } $meta = AF_Translate_Path_Resolver::resolve_workspace_meta( $component, $type, $locale ); $meta['target_dir'] = wp_normalize_path( $meta['target_dir'] ); $meta['po_target'] = wp_normalize_path( $meta['po_target'] ); $meta['mo_target'] = wp_normalize_path( $meta['mo_target'] ); if ( ! is_dir( $meta['target_dir'] ) ) { wp_mkdir_p( $meta['target_dir'] ); } $meta['component'] = $component; $meta['domain'] = $component; $meta['locale'] = $locale; AF_Translate_Path_Resolver::run_case_migration( $meta, $locale, $component ); if ( class_exists( 'AF_Translate_Path_Validator' ) && ! AF_Translate_Path_Validator::validate_containment( dirname( $meta['po_target'] ), false ) ) { wp_send_json_error( esc_html__( 'Target routing location constraint safety violation flag.', 'af-translate' ) ); } if ( ! $overwrite ) { $file_exists_anywhere = file_exists( $meta['po_target'] ); if ( ! $file_exists_anywhere ) { foreach ( AF_Translate_Path_Resolver::get_bundle_candidates( $meta, $component, $locale ) as $candidate ) { if ( file_exists( AF_Translate_Path_Validator::resolve_canonical_path( $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 { if ( file_exists( $meta['po_target'] ) ) { wp_delete_file( $meta['po_target'] ); } if ( file_exists( $meta['mo_target'] ) ) { wp_delete_file( $meta['mo_target'] ); } } $_FILES['po_file_upload']['name'] = $meta['po_filename']; $custom_upload_dir_callback = function ( $uploads ) use ( $meta ) { $uploads['path'] = $meta['target_dir']; $uploads['url'] = $meta['target_dir']; $uploads['subdir'] = ''; $uploads['basedir'] = $meta['target_dir']; $uploads['baseurl'] = $meta['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'; } $move_file = wp_handle_upload( $_FILES['po_file_upload'], array( 'test_form' => false, 'test_type' => false ) ); remove_filter( 'upload_dir', $custom_upload_dir_callback ); if ( ! empty( $move_file['file'] ) ) { AF_Translate_Artifact_Compiler::compile_all( $move_file['file'], $meta ); if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { /* translators: 1: Target locale code, 2: Target PO file name */ $log_message = sprintf( __( 'Imported external localization mapping dataset for language "%1$s" (%2$s).', 'af-translate' ), strtoupper( $locale ), $meta['po_filename'] ); AF_Translate_Admin::log_activity( $log_message, $component ); } 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 process_sync() { 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_file_name( 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' ) ); } // Initial metadata calculation to resolve pure locale string $meta = AF_Translate_Path_Resolver::resolve_workspace_meta( $component, $type, 'tmp_loc' ); $pure_name = basename( $filename, '.po' ); $locale = $meta['is_main_core'] ? $pure_name : str_replace( $component . '-', '', $pure_name ); $locale = preg_replace( '/[^a-zA-Z0-9_-]/', '', $locale ); // Fully resolve and normalize workspace metadata paths $meta = AF_Translate_Path_Resolver::resolve_workspace_meta( $component, $type, $locale ); $meta['target_dir'] = wp_normalize_path( $meta['target_dir'] ); $meta['po_target'] = wp_normalize_path( $meta['po_target'] ); $meta['mo_target'] = wp_normalize_path( $meta['mo_target'] ); if ( ! is_dir( $meta['target_dir'] ) ) { wp_mkdir_p( $meta['target_dir'] ); } $meta['component'] = $component; $meta['domain'] = $component; $meta['locale'] = $locale; AF_Translate_Path_Resolver::run_case_migration( $meta, $locale, $component ); // Discover pre-existing bundle file candidates (included files in themes/plugins) $fallback_po = ''; foreach ( AF_Translate_Path_Resolver::get_bundle_candidates( $meta, $component, $locale ) as $candidate ) { $canonical_candidate = wp_normalize_path( AF_Translate_Path_Validator::resolve_canonical_path( $candidate ) ); if ( file_exists( $canonical_candidate ) ) { $fallback_po = $canonical_candidate; break; } } // Mirror included PO file to sandbox target if sandbox file does not exist yet if ( ! file_exists( $meta['po_target'] ) && ! empty( $fallback_po ) ) { $can_copy = class_exists( 'AF_Translate_Path_Validator' ) ? AF_Translate_Path_Validator::validate_containment( $fallback_po, false ) : true; if ( $can_copy ) { @copy( $fallback_po, $meta['po_target'] ); } } // Containment check on target sandbox directory $target_dir_to_check = wp_normalize_path( dirname( $meta['po_target'] ) ); if ( class_exists( 'AF_Translate_Path_Validator' ) ) { if ( ! AF_Translate_Path_Validator::validate_containment( $target_dir_to_check, false ) ) { wp_send_json_error( esc_html__( 'Path constraint containment breach flag raised during synchronization.', 'af-translate' ) ); } } // MASTER POT ROUTING $pot_file = ''; $pot_file_name = $meta['is_main_core'] ? 'wordpress.pot' : "{$component}.pot"; $pot_candidates = array( $meta['target_dir'] . '/' . $pot_file_name ); if ( $meta['is_core'] ) { $pot_candidates[] = wp_normalize_path( trailingslashit( WP_LANG_DIR ) ) . $pot_file_name; } else { $pot_candidates[] = wp_normalize_path( $meta['base_dir'] . '/languages/' . $pot_file_name ); $pot_candidates[] = wp_normalize_path( $meta['base_dir'] . '/' . $pot_file_name ); if ( is_dir( $meta['base_dir'] . '/languages' ) ) { $lang_pots = glob( wp_normalize_path( trailingslashit( $meta['base_dir'] . '/languages' ) ) . '*.pot' ); if ( ! empty( $lang_pots ) && is_array( $lang_pots ) ) { $pot_candidates = array_merge( $pot_candidates, array_map( 'wp_normalize_path', $lang_pots ) ); } } if ( is_dir( $meta['base_dir'] ) ) { $root_pots = glob( wp_normalize_path( trailingslashit( $meta['base_dir'] ) ) . '*.pot' ); if ( ! empty( $root_pots ) && is_array( $root_pots ) ) { $pot_candidates = array_merge( $pot_candidates, array_map( 'wp_normalize_path', $root_pots ) ); } } } foreach ( $pot_candidates as $candidate ) { if ( is_string( $candidate ) ) { $canonical_pot = wp_normalize_path( AF_Translate_Path_Validator::resolve_canonical_path( $candidate ) ); if ( file_exists( $canonical_pot ) ) { $pot_file = $canonical_pot; break; } } } $sandbox_sync_pot = $meta['target_dir'] . '/' . $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 ) && 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 = wp_normalize_path( $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 ( class_exists( 'AF_Translate_Path_Validator' ) && ! 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( $meta['po_target'], $pot_file ); if ( false !== $sync_results ) { AF_Translate_Artifact_Compiler::compile_all( $meta['po_target'], $meta ); if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { /* translators: 1: Target locale code, 2: Target PO file name, 3: Number of new strings added, 4: Number of obsolete strings dropped */ $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 ), basename( $meta['po_target'] ), intval( $sync_results['new'] ), intval( $sync_results['obsolete'] ) ); AF_Translate_Admin::log_activity( $log_message, $component ); } /* translators: 1: Number of new strings added, 2: Number of obsolete strings removed */ $message = sprintf( esc_html__( 'Synchronization completed successfully. %1$d new strings added, %2$d obsolete strings removed.', 'af-translate' ), intval( $sync_results['new'] ), intval( $sync_results['obsolete'] ) ); wp_send_json_success( $message ); } wp_send_json_error( esc_html__( 'Synchronization processing failed: Unable to merge file streams.', 'af-translate' ) ); } }