Diff: STRATO-apps/wordpress_03/app/wp-includes/l10n/class-wp-translation-file-mo.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
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 + }
240 +