[ Index ] |
PHP Cross Reference of BackPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Class for working with MO files 4 * 5 * @version $Id: mo.php 1180 2020-08-10 10:18:38Z xknown $ 6 * @package pomo 7 * @subpackage mo 8 */ 9 10 require_once __DIR__ . '/translations.php'; 11 require_once __DIR__ . '/streams.php'; 12 13 if ( ! class_exists( 'MO', false ) ) : 14 class MO extends Gettext_Translations { 15 16 var $_nplurals = 2; 17 18 /** 19 * Loaded MO file. 20 * 21 * @var string 22 */ 23 private $filename = ''; 24 25 /** 26 * Returns the loaded MO file. 27 * 28 * @return string The loaded MO file. 29 */ 30 public function get_filename() { 31 return $this->filename; 32 } 33 34 /** 35 * Fills up with the entries from MO file $filename 36 * 37 * @param string $filename MO file to load 38 * @return bool True if the import from file was successful, otherwise false. 39 */ 40 function import_from_file( $filename ) { 41 $reader = new POMO_FileReader( $filename ); 42 43 if ( ! $reader->is_resource() ) { 44 return false; 45 } 46 47 $this->filename = (string) $filename; 48 49 return $this->import_from_reader( $reader ); 50 } 51 52 /** 53 * @param string $filename 54 * @return bool 55 */ 56 function export_to_file( $filename ) { 57 $fh = fopen( $filename, 'wb' ); 58 if ( ! $fh ) { 59 return false; 60 } 61 $res = $this->export_to_file_handle( $fh ); 62 fclose( $fh ); 63 return $res; 64 } 65 66 /** 67 * @return string|false 68 */ 69 function export() { 70 $tmp_fh = fopen( 'php://temp', 'r+' ); 71 if ( ! $tmp_fh ) { 72 return false; 73 } 74 $this->export_to_file_handle( $tmp_fh ); 75 rewind( $tmp_fh ); 76 return stream_get_contents( $tmp_fh ); 77 } 78 79 /** 80 * @param Translation_Entry $entry 81 * @return bool 82 */ 83 function is_entry_good_for_export( $entry ) { 84 if ( empty( $entry->translations ) ) { 85 return false; 86 } 87 88 if ( ! array_filter( $entry->translations ) ) { 89 return false; 90 } 91 92 return true; 93 } 94 95 /** 96 * @param resource $fh 97 * @return true 98 */ 99 function export_to_file_handle( $fh ) { 100 $entries = array_filter( $this->entries, array( $this, 'is_entry_good_for_export' ) ); 101 ksort( $entries ); 102 $magic = 0x950412de; 103 $revision = 0; 104 $total = count( $entries ) + 1; // All the headers are one entry. 105 $originals_lenghts_addr = 28; 106 $translations_lenghts_addr = $originals_lenghts_addr + 8 * $total; 107 $size_of_hash = 0; 108 $hash_addr = $translations_lenghts_addr + 8 * $total; 109 $current_addr = $hash_addr; 110 fwrite( 111 $fh, 112 pack( 113 'V*', 114 $magic, 115 $revision, 116 $total, 117 $originals_lenghts_addr, 118 $translations_lenghts_addr, 119 $size_of_hash, 120 $hash_addr 121 ) 122 ); 123 fseek( $fh, $originals_lenghts_addr ); 124 125 // Headers' msgid is an empty string. 126 fwrite( $fh, pack( 'VV', 0, $current_addr ) ); 127 $current_addr++; 128 $originals_table = "\0"; 129 130 $reader = new POMO_Reader(); 131 132 foreach ( $entries as $entry ) { 133 $originals_table .= $this->export_original( $entry ) . "\0"; 134 $length = $reader->strlen( $this->export_original( $entry ) ); 135 fwrite( $fh, pack( 'VV', $length, $current_addr ) ); 136 $current_addr += $length + 1; // Account for the NULL byte after. 137 } 138 139 $exported_headers = $this->export_headers(); 140 fwrite( $fh, pack( 'VV', $reader->strlen( $exported_headers ), $current_addr ) ); 141 $current_addr += strlen( $exported_headers ) + 1; 142 $translations_table = $exported_headers . "\0"; 143 144 foreach ( $entries as $entry ) { 145 $translations_table .= $this->export_translations( $entry ) . "\0"; 146 $length = $reader->strlen( $this->export_translations( $entry ) ); 147 fwrite( $fh, pack( 'VV', $length, $current_addr ) ); 148 $current_addr += $length + 1; 149 } 150 151 fwrite( $fh, $originals_table ); 152 fwrite( $fh, $translations_table ); 153 return true; 154 } 155 156 /** 157 * @param Translation_Entry $entry 158 * @return string 159 */ 160 function export_original( $entry ) { 161 // TODO: Warnings for control characters. 162 $exported = $entry->singular; 163 if ( $entry->is_plural ) { 164 $exported .= "\0" . $entry->plural; 165 } 166 if ( $entry->context ) { 167 $exported = $entry->context . "\4" . $exported; 168 } 169 return $exported; 170 } 171 172 /** 173 * @param Translation_Entry $entry 174 * @return string 175 */ 176 function export_translations( $entry ) { 177 // TODO: Warnings for control characters. 178 return $entry->is_plural ? implode( "\0", $entry->translations ) : $entry->translations[0]; 179 } 180 181 /** 182 * @return string 183 */ 184 function export_headers() { 185 $exported = ''; 186 foreach ( $this->headers as $header => $value ) { 187 $exported .= "$header: $value\n"; 188 } 189 return $exported; 190 } 191 192 /** 193 * @param int $magic 194 * @return string|false 195 */ 196 function get_byteorder( $magic ) { 197 // The magic is 0x950412de. 198 199 // bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565 200 $magic_little = (int) - 1794895138; 201 $magic_little_64 = (int) 2500072158; 202 // 0xde120495 203 $magic_big = ( (int) - 569244523 ) & 0xFFFFFFFF; 204 if ( $magic_little == $magic || $magic_little_64 == $magic ) { 205 return 'little'; 206 } elseif ( $magic_big == $magic ) { 207 return 'big'; 208 } else { 209 return false; 210 } 211 } 212 213 /** 214 * @param POMO_FileReader $reader 215 * @return bool True if the import was successful, otherwise false. 216 */ 217 function import_from_reader( $reader ) { 218 $endian_string = MO::get_byteorder( $reader->readint32() ); 219 if ( false === $endian_string ) { 220 return false; 221 } 222 $reader->setEndian( $endian_string ); 223 224 $endian = ( 'big' === $endian_string ) ? 'N' : 'V'; 225 226 $header = $reader->read( 24 ); 227 if ( $reader->strlen( $header ) != 24 ) { 228 return false; 229 } 230 231 // Parse header. 232 $header = unpack( "{$endian}revision/{$endian}total/{$endian}originals_lenghts_addr/{$endian}translations_lenghts_addr/{$endian}hash_length/{$endian}hash_addr", $header ); 233 if ( ! is_array( $header ) ) { 234 return false; 235 } 236 237 // Support revision 0 of MO format specs, only. 238 if ( 0 != $header['revision'] ) { 239 return false; 240 } 241 242 // Seek to data blocks. 243 $reader->seekto( $header['originals_lenghts_addr'] ); 244 245 // Read originals' indices. 246 $originals_lengths_length = $header['translations_lenghts_addr'] - $header['originals_lenghts_addr']; 247 if ( $originals_lengths_length != $header['total'] * 8 ) { 248 return false; 249 } 250 251 $originals = $reader->read( $originals_lengths_length ); 252 if ( $reader->strlen( $originals ) != $originals_lengths_length ) { 253 return false; 254 } 255 256 // Read translations' indices. 257 $translations_lenghts_length = $header['hash_addr'] - $header['translations_lenghts_addr']; 258 if ( $translations_lenghts_length != $header['total'] * 8 ) { 259 return false; 260 } 261 262 $translations = $reader->read( $translations_lenghts_length ); 263 if ( $reader->strlen( $translations ) != $translations_lenghts_length ) { 264 return false; 265 } 266 267 // Transform raw data into set of indices. 268 $originals = $reader->str_split( $originals, 8 ); 269 $translations = $reader->str_split( $translations, 8 ); 270 271 // Skip hash table. 272 $strings_addr = $header['hash_addr'] + $header['hash_length'] * 4; 273 274 $reader->seekto( $strings_addr ); 275 276 $strings = $reader->read_all(); 277 $reader->close(); 278 279 for ( $i = 0; $i < $header['total']; $i++ ) { 280 $o = unpack( "{$endian}length/{$endian}pos", $originals[ $i ] ); 281 $t = unpack( "{$endian}length/{$endian}pos", $translations[ $i ] ); 282 if ( ! $o || ! $t ) { 283 return false; 284 } 285 286 // Adjust offset due to reading strings to separate space before. 287 $o['pos'] -= $strings_addr; 288 $t['pos'] -= $strings_addr; 289 290 $original = $reader->substr( $strings, $o['pos'], $o['length'] ); 291 $translation = $reader->substr( $strings, $t['pos'], $t['length'] ); 292 293 if ( '' === $original ) { 294 $this->set_headers( $this->make_headers( $translation ) ); 295 } else { 296 $entry = &$this->make_entry( $original, $translation ); 297 $this->entries[ $entry->key() ] = &$entry; 298 } 299 } 300 return true; 301 } 302 303 /** 304 * Build a Translation_Entry from original string and translation strings, 305 * found in a MO file 306 * 307 * @static 308 * @param string $original original string to translate from MO file. Might contain 309 * 0x04 as context separator or 0x00 as singular/plural separator 310 * @param string $translation translation string from MO file. Might contain 311 * 0x00 as a plural translations separator 312 * @return Translation_Entry Entry instance. 313 */ 314 function &make_entry( $original, $translation ) { 315 $entry = new Translation_Entry(); 316 // Look for context, separated by \4. 317 $parts = explode( "\4", $original ); 318 if ( isset( $parts[1] ) ) { 319 $original = $parts[1]; 320 $entry->context = $parts[0]; 321 } 322 // Look for plural original. 323 $parts = explode( "\0", $original ); 324 $entry->singular = $parts[0]; 325 if ( isset( $parts[1] ) ) { 326 $entry->is_plural = true; 327 $entry->plural = $parts[1]; 328 } 329 // Plural translations are also separated by \0. 330 $entry->translations = explode( "\0", $translation ); 331 return $entry; 332 } 333 334 /** 335 * @param int $count 336 * @return string 337 */ 338 function select_plural_form( $count ) { 339 return $this->gettext_select_plural_form( $count ); 340 } 341 342 /** 343 * @return int 344 */ 345 function get_plural_forms_count() { 346 return $this->_nplurals; 347 } 348 } 349 endif;
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Oct 9 01:00:57 2024 | Cross-referenced by PHPXref 0.7.1 |