[ Index ]

PHP Cross Reference of BackPress

title

Body

[close]

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

   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;


Generated: Thu Dec 26 01:01:01 2024 Cross-referenced by PHPXref 0.7.1