Diff: STRATO-apps/wordpress_03/app/wp-includes/l10n/class-wp-translation-file-mo.php
Keine Baseline-Datei – Diff nur gegen leer.
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
+