Make WordPress Core


Ignore:
Timestamp:
11/30/2017 11:09:33 PM (7 years ago)
Author:
pento
Message:

Code is Poetry.
WordPress' code just... wasn't.
This is now dealt with.

Props jrf, pento, netweb, GaryJ, jdgrimes, westonruter, Greg Sherwood from PHPCS, and everyone who's ever contributed to WPCS and PHPCS.
Fixes #41057.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/pomo/mo.php

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