$base . '/languages', 'system' => $lang_dir . '/' . $type . 's', 'custom' => wp_normalize_path( trailingslashit( $upload_dir['basedir'] ) . "af-translate/{$sub_folder}" ) ); } /** * Aggregates configuration files across disk architectures safely. */ public static function get_component_summary( $slug, $type ) { if ( ! current_user_can( 'manage_options' ) ) { return array( 'text_domain' => sanitize_key( $slug ), 'pot_exists' => false, 'pot_file' => '', 'po_count' => 0, 'po_collection' => array() ); } $slug = sanitize_key( $slug ); $type = self::clean_component_type( $type ); $dirs = self::locate_translation_dirs( $slug, $type ); $pot_files = array(); $po_files = array(); foreach ( $dirs as $key => $dir ) { if ( is_dir( $dir ) && self::verify_path_containment( $dir ) ) { $found_pots = glob( trailingslashit( $dir ) . "*.pot" ); if ( ! empty( $found_pots ) && is_array( $found_pots ) ) { foreach ( $found_pots as $pot ) { $pot_name = basename( $pot ); if ( 'system' !== $key || $pot_name === "{$slug}.pot" ) { $pot_files[] = wp_normalize_path( $pot ); } } } $found_pos = glob( trailingslashit( $dir ) . "*.po" ); if ( ! empty( $found_pos ) && is_array( $found_pos ) ) { foreach ( $found_pos as $po ) { $filename = basename( $po ); if ( 'author' === $key ) { $po_files[] = wp_normalize_path( $po ); } else { if ( stripos( $filename, "{$slug}-" ) === 0 ) { $po_files[] = wp_normalize_path( $po ); } } } } } } $pot_files = array_unique( array_filter( $pot_files ) ); $po_files = array_unique( array_filter( $po_files ) ); $primary_pot = ''; if ( ! empty( $pot_files ) ) { foreach ( $pot_files as $pot ) { if ( basename( $pot ) === "{$slug}.pot" ) { $primary_pot = $pot; break; } } if ( empty( $primary_pot ) ) { $reset_pot = reset( $pot_files ); if ( is_string( $reset_pot ) ) { $primary_pot = $reset_pot; } } } return array( 'text_domain' => $slug, 'pot_exists' => ! empty( $primary_pot ), 'pot_file' => $primary_pot, 'po_count' => count( $po_files ), 'po_collection' => $po_files ); } /** * Reads, tokenizes, and aggregates real completion matrices using native WP Core POMO classes. */ public static function analyze_po_file( $file_path ) { $metrics = array( 'total' => 0, 'translated' => 0, 'percentage' => 0 ); // 1. Security & Containment Guards if ( ! current_user_can( 'manage_options' ) || ! self::verify_path_containment( $file_path ) ) { return $metrics; } if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) { return $metrics; } // 2. Include Native WordPress POMO library components if missing if ( ! class_exists( 'PO' ) ) { require_once ABSPATH . 'wp-includes/pomo/po.php'; } // 3. Initialize core parser and import the stream $po_parser = new PO(); if ( false === $po_parser->import_from_file( $file_path ) ) { return $metrics; } // 4. If the file has entries, iterate through the Core Translation_Entry objects if ( ! empty( $po_parser->entries ) && is_array( $po_parser->entries ) ) { foreach ( $po_parser->entries as $entry ) { // Core maps file meta headers to an empty msgid string. Skip cleanly. if ( '' === $entry->singular ) { continue; } $metrics['total']++; // 5. FIXED: Safely loop inside the inner translation collection array if ( ! empty( $entry->translations ) && is_array( $entry->translations ) ) { $is_valid_translation = false; foreach ( $entry->translations as $string_variant ) { if ( '' !== trim( (string) $string_variant ) ) { $is_valid_translation = true; break; // Stop looking if at least one variant configuration is populated } } if ( $is_valid_translation ) { $metrics['translated']++; } } } } // 6. Compute completion metrics if ( $metrics['total'] > 0 ) { $metrics['percentage'] = (int) round( ( $metrics['translated'] / $metrics['total'] ) * 100 ); } return $metrics; } }