[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * I18N: WP_Translation_File_MO class. 4 * 5 * @package WordPress 6 * @subpackage I18N 7 * @since 6.5.0 8 */ 9 10 /** 11 * Class WP_Translation_File_MO. 12 * 13 * @since 6.5.0 14 */ 15 class WP_Translation_File_MO extends WP_Translation_File { 16 /** 17 * Endian value. 18 * 19 * V for little endian, N for big endian, or false. 20 * 21 * Used for unpack(). 22 * 23 * @since 6.5.0 24 * @var false|'V'|'N' 25 */ 26 protected $uint32 = false; 27 28 /** 29 * The magic number of the GNU message catalog format. 30 * 31 * @since 6.5.0 32 * @var int 33 */ 34 const MAGIC_MARKER = 0x950412de; 35 36 /** 37 * Detects endian and validates file. 38 * 39 * @since 6.5.0 40 * 41 * @param string $header File contents. 42 * @return false|'V'|'N' V for little endian, N for big endian, or false on failure. 43 */ 44 protected function detect_endian_and_validate_file( string $header ) { 45 $big = unpack( 'N', $header ); 46 47 if ( false === $big ) { 48 return false; 49 } 50 51 $big = reset( $big ); 52 53 if ( false === $big ) { 54 return false; 55 } 56 57 $little = unpack( 'V', $header ); 58 59 if ( false === $little ) { 60 return false; 61 } 62 63 $little = reset( $little ); 64 65 if ( false === $little ) { 66 return false; 67 } 68 69 // Force cast to an integer as it can be a float on x86 systems. See https://core.trac.wordpress.org/ticket/60678. 70 if ( (int) self::MAGIC_MARKER === $big ) { 71 return 'N'; 72 } 73 74 // Force cast to an integer as it can be a float on x86 systems. See https://core.trac.wordpress.org/ticket/60678. 75 if ( (int) self::MAGIC_MARKER === $little ) { 76 return 'V'; 77 } 78 79 $this->error = 'Magic marker does not exist'; 80 return false; 81 } 82 83 /** 84 * Parses the file. 85 * 86 * @since 6.5.0 87 * 88 * @return bool True on success, false otherwise. 89 */ 90 protected function parse_file(): bool { 91 $this->parsed = true; 92 93 $file_contents = file_get_contents( $this->file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents 94 95 if ( false === $file_contents ) { 96 return false; 97 } 98 99 $file_length = strlen( $file_contents ); 100 101 if ( $file_length < 24 ) { 102 $this->error = 'Invalid data'; 103 return false; 104 } 105 106 $this->uint32 = $this->detect_endian_and_validate_file( substr( $file_contents, 0, 4 ) ); 107 108 if ( false === $this->uint32 ) { 109 return false; 110 } 111 112 $offsets = substr( $file_contents, 4, 24 ); 113 114 if ( false === $offsets ) { 115 return false; 116 } 117 118 $offsets = unpack( "{$this->uint32}rev/{$this->uint32}total/{$this->uint32}originals_addr/{$this->uint32}translations_addr/{$this->uint32}hash_length/{$this->uint32}hash_addr", $offsets ); 119 120 if ( false === $offsets ) { 121 return false; 122 } 123 124 $offsets['originals_length'] = $offsets['translations_addr'] - $offsets['originals_addr']; 125 $offsets['translations_length'] = $offsets['hash_addr'] - $offsets['translations_addr']; 126 127 if ( $offsets['rev'] > 0 ) { 128 $this->error = 'Unsupported revision'; 129 return false; 130 } 131 132 if ( $offsets['translations_addr'] > $file_length || $offsets['originals_addr'] > $file_length ) { 133 $this->error = 'Invalid data'; 134 return false; 135 } 136 137 // Load the Originals. 138 $original_data = str_split( substr( $file_contents, $offsets['originals_addr'], $offsets['originals_length'] ), 8 ); 139 $translations_data = str_split( substr( $file_contents, $offsets['translations_addr'], $offsets['translations_length'] ), 8 ); 140 141 foreach ( array_keys( $original_data ) as $i ) { 142 $o = unpack( "{$this->uint32}length/{$this->uint32}pos", $original_data[ $i ] ); 143 $t = unpack( "{$this->uint32}length/{$this->uint32}pos", $translations_data[ $i ] ); 144 145 if ( false === $o || false === $t ) { 146 continue; 147 } 148 149 $original = substr( $file_contents, $o['pos'], $o['length'] ); 150 $translation = substr( $file_contents, $t['pos'], $t['length'] ); 151 // GlotPress bug. 152 $translation = rtrim( $translation, "\0" ); 153 154 // Metadata about the MO file is stored in the first translation entry. 155 if ( '' === $original ) { 156 foreach ( explode( "\n", $translation ) as $meta_line ) { 157 if ( '' === $meta_line || ! str_contains( $meta_line, ':' ) ) { 158 continue; 159 } 160 161 list( $name, $value ) = array_map( 'trim', explode( ':', $meta_line, 2 ) ); 162 163 $this->headers[ strtolower( $name ) ] = $value; 164 } 165 } else { 166 /* 167 * In MO files, the key normally contains both singular and plural versions. 168 * However, this just adds the singular string for lookup, 169 * which caters for cases where both __( 'Product' ) and _n( 'Product', 'Products' ) 170 * are used and the translation is expected to be the same for both. 171 */ 172 $parts = explode( "\0", (string) $original ); 173 174 $this->entries[ $parts[0] ] = $translation; 175 } 176 } 177 178 return true; 179 } 180 181 /** 182 * Exports translation contents as a string. 183 * 184 * @since 6.5.0 185 * 186 * @return string Translation file contents. 187 */ 188 public function export(): string { 189 // Prefix the headers as the first key. 190 $headers_string = ''; 191 foreach ( $this->headers as $header => $value ) { 192 $headers_string .= "{$header}: $value\n"; 193 } 194 $entries = array_merge( array( '' => $headers_string ), $this->entries ); 195 $entry_count = count( $entries ); 196 197 if ( false === $this->uint32 ) { 198 $this->uint32 = 'V'; 199 } 200 201 $bytes_for_entries = $entry_count * 4 * 2; 202 // Pair of 32bit ints per entry. 203 $originals_addr = 28; /* header */ 204 $translations_addr = $originals_addr + $bytes_for_entries; 205 $hash_addr = $translations_addr + $bytes_for_entries; 206 $entry_offsets = $hash_addr; 207 208 $file_header = pack( 209 $this->uint32 . '*', 210 // Force cast to an integer as it can be a float on x86 systems. See https://core.trac.wordpress.org/ticket/60678. 211 (int) self::MAGIC_MARKER, 212 0, /* rev */ 213 $entry_count, 214 $originals_addr, 215 $translations_addr, 216 0, /* hash_length */ 217 $hash_addr 218 ); 219 220 $o_entries = ''; 221 $t_entries = ''; 222 $o_addr = ''; 223 $t_addr = ''; 224 225 foreach ( array_keys( $entries ) as $original ) { 226 $o_addr .= pack( $this->uint32 . '*', strlen( $original ), $entry_offsets ); 227 $entry_offsets += strlen( $original ) + 1; 228 $o_entries .= $original . "\0"; 229 } 230 231 foreach ( $entries as $translations ) { 232 $t_addr .= pack( $this->uint32 . '*', strlen( $translations ), $entry_offsets ); 233 $entry_offsets += strlen( $translations ) + 1; 234 $t_entries .= $translations . "\0"; 235 } 236 237 return $file_header . $o_addr . $t_addr . $o_entries . $t_entries; 238 } 239 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Sat Dec 21 08:20:01 2024 | Cross-referenced by PHPXref |