home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / pomo / mo.php < prev    next >
Encoding:
PHP Script  |  2016-10-26  |  8.3 KB  |  324 lines

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