[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/pomo/ -> mo.php (source)

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


Generated : Wed May 8 08:20:02 2024 Cross-referenced by PHPXref