[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/l10n/ -> class-wp-translation-file-mo.php (source)

   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  }


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref