Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
SimpleFFS.php
Go to the documentation of this file.
1<?php
17use UtfNormal\Validator;
18
19class SimpleFFS implements FFS {
20 public function supportsFuzzy() {
21 return 'no';
22 }
23
24 public function getFileExtensions() {
25 return [];
26 }
27
29 protected $group;
30 protected $writePath;
35 protected $extra;
36
37 private const RECORD_SEPARATOR = "\0";
38 private const PART_SEPARATOR = "\0\0\0\0";
39
40 public function __construct( FileBasedMessageGroup $group ) {
41 $this->setGroup( $group );
42 $conf = $group->getConfiguration();
43 $this->extra = $conf['FILES'];
44 }
45
47 public function setGroup( FileBasedMessageGroup $group ) {
48 $this->group = $group;
49 }
50
52 public function getGroup() {
53 return $this->group;
54 }
55
57 public function setWritePath( $writePath ) {
58 $this->writePath = $writePath;
59 }
60
62 public function getWritePath() {
63 return $this->writePath;
64 }
65
76 public function exists( $code = false ) {
77 if ( $code === false ) {
78 $code = $this->group->getSourceLanguage();
79 }
80
81 $filename = $this->group->getSourceFilePath( $code );
82 if ( $filename === null ) {
83 return false;
84 }
85
86 return file_exists( $filename );
87 }
88
97 public function read( $code ) {
98 if ( !$this->isGroupFfsReadable() ) {
99 return [];
100 }
101
102 if ( !$this->exists( $code ) ) {
103 return false;
104 }
105
106 $filename = $this->group->getSourceFilePath( $code );
107 $input = file_get_contents( $filename );
108 if ( $input === false ) {
109 throw new MWException( "Unable to read file $filename." );
110 }
111
112 if ( !StringUtils::isUtf8( $input ) ) {
113 throw new MWException( "Contents of $filename are not valid utf-8." );
114 }
115
116 $input = Validator::cleanUp( $input );
117
118 // Strip BOM mark
119 $input = ltrim( $input, "\u{FEFF}" );
120
121 try {
122 return $this->readFromVariable( $input );
123 } catch ( Exception $e ) {
124 throw new MWException( "Parsing $filename failed: " . $e->getMessage() );
125 }
126 }
127
136 public function readFromVariable( $data ) {
137 $parts = explode( self::PART_SEPARATOR, $data );
138
139 if ( count( $parts ) !== 2 ) {
140 throw new MWException( 'Wrong number of parts.' );
141 }
142
143 list( $authorsPart, $messagesPart ) = $parts;
144 $authors = explode( self::RECORD_SEPARATOR, $authorsPart );
145 $messages = [];
146
147 foreach ( explode( self::RECORD_SEPARATOR, $messagesPart ) as $line ) {
148 if ( $line === '' ) {
149 continue;
150 }
151
152 $lineParts = explode( '=', $line, 2 );
153
154 if ( count( $lineParts ) !== 2 ) {
155 throw new MWException( "Wrong number of parts in line $line." );
156 }
157
158 list( $key, $message ) = $lineParts;
159 $key = trim( $key );
160 $messages[$key] = $message;
161 }
162
163 $messages = $this->group->getMangler()->mangleArray( $messages );
164
165 return [
166 'AUTHORS' => $authors,
167 'MESSAGES' => $messages,
168 ];
169 }
170
177 public function write( MessageCollection $collection ) {
178 $writePath = $this->writePath;
179
180 if ( $writePath === null ) {
181 throw new MWException( 'Write path is not set.' );
182 }
183
184 if ( !file_exists( $writePath ) ) {
185 throw new MWException( "Write path '$writePath' does not exist." );
186 }
187
188 if ( !is_writable( $writePath ) ) {
189 throw new MWException( "Write path '$writePath' is not writable." );
190 }
191
192 $targetFile = $writePath . '/' . $this->group->getTargetFilename( $collection->code );
193
194 $targetFileExists = file_exists( $targetFile );
195
196 if ( $targetFileExists ) {
197 $this->tryReadSource( $targetFile, $collection );
198 } else {
199 $sourceFile = $this->group->getSourceFilePath( $collection->code );
200 $this->tryReadSource( $sourceFile, $collection );
201 }
202
203 $output = $this->writeReal( $collection );
204 if ( !$output ) {
205 return;
206 }
207
208 // Some file formats might have changing parts, such as timestamp.
209 // This allows the file handler to skip updating files, where only
210 // the timestamp would change.
211 if ( $targetFileExists ) {
212 $oldContent = $this->tryReadFile( $targetFile );
213 if ( !$this->shouldOverwrite( $oldContent, $output ) ) {
214 return;
215 }
216 }
217
218 wfMkdirParents( dirname( $targetFile ), null, __METHOD__ );
219 file_put_contents( $targetFile, $output );
220 }
221
228 public function writeIntoVariable( MessageCollection $collection ) {
229 $sourceFile = $this->group->getSourceFilePath( $collection->code );
230 $this->tryReadSource( $sourceFile, $collection );
231
232 return $this->writeReal( $collection );
233 }
234
239 protected function writeReal( MessageCollection $collection ) {
240 $output = '';
241
242 $authors = $collection->getAuthors();
243 $authors = $this->filterAuthors( $authors, $collection->code );
244
245 $output .= implode( self::RECORD_SEPARATOR, $authors );
246 $output .= self::PART_SEPARATOR;
247
248 $mangler = $this->group->getMangler();
249
251 foreach ( $collection as $key => $m ) {
252 $key = $mangler->unmangle( $key );
253 $trans = $m->translation();
254 $output .= "$key=$trans" . self::RECORD_SEPARATOR;
255 }
256
257 return $output;
258 }
259
269 protected function tryReadSource( $filename, MessageCollection $collection ) {
270 if ( !$this->isGroupFfsReadable() ) {
271 return;
272 }
273
274 $sourceText = $this->tryReadFile( $filename );
275
276 // No need to do anything in SimpleFFS if it's false,
277 // it only reads author data from it.
278 if ( $sourceText !== false ) {
279 $sourceData = $this->readFromVariable( $sourceText );
280
281 if ( isset( $sourceData['AUTHORS'] ) ) {
282 $collection->addCollectionAuthors( $sourceData['AUTHORS'] );
283 }
284 }
285 }
286
297 protected function tryReadFile( $filename ) {
298 if ( !$filename ) {
299 return false;
300 }
301
302 if ( !file_exists( $filename ) ) {
303 return false;
304 }
305
306 if ( !is_readable( $filename ) ) {
307 throw new MWException( "File $filename is not readable." );
308 }
309
310 $data = file_get_contents( $filename );
311 if ( $data === false ) {
312 throw new MWException( "Unable to read file $filename." );
313 }
314
315 return $data;
316 }
317
325 public function filterAuthors( array $authors, $code ) {
326 $groupId = $this->group->getId();
327 $configHelper = Services::getInstance()->getConfigHelper();
328
329 foreach ( $authors as $i => $v ) {
330 if ( $configHelper->isAuthorExcluded( $groupId, $code, $v ) ) {
331 unset( $authors[$i] );
332 }
333 }
334
335 return array_values( $authors );
336 }
337
345 public static function fixNewLines( $data ) {
346 $data = str_replace( "\r\n", "\n", $data );
347 $data = str_replace( "\r", "\n", $data );
348
349 return $data;
350 }
351
352 public function isContentEqual( $a, $b ) {
353 return $a === $b;
354 }
355
356 public function shouldOverwrite( $a, $b ) {
357 return true;
358 }
359
366 public function isGroupFfsReadable(): bool {
367 try {
368 $ffs = $this->group->getFFS();
369 } catch ( RunTimeException $e ) {
370 if ( $e->getCode() === FileBasedMessageGroup::NO_FFS_CLASS ) {
371 return false;
372 }
373
374 throw $e;
375 }
376
377 return get_class( $ffs ) === get_class( $this );
378 }
379}
This class implements default behavior for file based message groups.
Minimal service container.
Definition Services.php:38
Core message collection class.
getAuthors()
Lists all translators that have contributed to the latest revisions of each translation.
addCollectionAuthors( $authors, $mode='append')
Add external authors (usually from the file).
filterAuthors(array $authors, $code)
Remove excluded authors.
exists( $code=false)
Returns true if the file for this message group in a given language exists.
Definition SimpleFFS.php:76
isGroupFfsReadable()
Check if the file format of the current group is readable by the file format system.
tryReadSource( $filename, MessageCollection $collection)
This tries to pick up external authors in the source files so that they are not lost if those authors...
shouldOverwrite( $a, $b)
Allows to skip writing the export output into a file.
isContentEqual( $a, $b)
Checks whether two strings are equal.
supportsFuzzy()
Query the capabilities of this FFS.
Definition SimpleFFS.php:20
writeReal(MessageCollection $collection)
read( $code)
Reads messages from the file in a given language and returns an array of AUTHORS, MESSAGES and possib...
Definition SimpleFFS.php:97
$extra
Stores the FILES section of the YAML configuration, which can be accessed for extra FFS class specifi...
Definition SimpleFFS.php:35
static fixNewLines( $data)
Replaces all Windows and Mac line endings with Unix line endings.
writeIntoVariable(MessageCollection $collection)
Read a collection and return it as a SimpleFFS formatted string.
getFileExtensions()
Return the commonly used file extensions for these formats.
Definition SimpleFFS.php:24
setGroup(FileBasedMessageGroup $group)
Definition SimpleFFS.php:47
write(MessageCollection $collection)
Write the collection to file.
setWritePath( $writePath)
Definition SimpleFFS.php:57
readFromVariable( $data)
Parse the message data given as a string in the SimpleFFS format and return it as an array of AUTHORS...
tryReadFile( $filename)
Read the contents of $filename and return it as a string.
Interface for file system support classes.
Definition FFS.php:19