summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Translate/TranslateTasks.php
blob: 559a93be548d902133f7d317b8e68fd60b8b6cf6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
<?php
/**
 * Tasks which encapsulate the processing of messages to requested
 * format for the web interface.
 *
 * @file
 * @author Niklas Laxström
 * @license GPL-2.0+
 */

/**
 * Basic implementation and interface for tasks.
 * Task is a combination of filters and output format that are applied to
 * messages of given message group in given language.
 */
abstract class TranslateTask {
	/**
	 * @var string Task identifier.
	 */
	protected $id = '__BUG__';

	// We need $id here because staticness prevents subclass overriding.
	/**
	 * Get label for task.
	 * @param string $id Task id
	 * @return string
	 */
	public static function labelForTask( $id ) {
		return wfMessage( 'translate-task-' . $id )->text();
	}

	/**
	 * Get task identifier.
	 * @return string
	 */
	public function getId() {
		return $this->id;
	}

	/**
	 * Indicates whether the task itself will hand the full output page,
	 * including headers. If false, the resulting html should be embedded
	 * to the page of calling context.
	 * @return bool
	 */
	public function plainOutput() {
		return false;
	}

	/**
	 * @var MessageGroup
	 */
	protected $group;

	/**
	 * @var MessageCollection
	 */
	protected $collection;

	/**
	 * @var array
	 */
	protected $options;

	/**
	 * @var array
	 */
	protected $nondefaults;

	/**
	 * @var IContextSource
	 */
	protected $context;

	/**
	 * @var array Offsets stored after the collection has been paged.
	 */
	protected $offsets;

	/**
	 * Constructor.
	 * @param MessageGroup $group Message group.
	 * @param array $options Options.
	 * @param array $nondefaults List of non-default options for links.
	 * @param IContextSource $context
	 */
	final public function init( MessageGroup $group, array $options, array $nondefaults,
		IContextSource $context
	) {
		$this->group = $group;
		$this->options = $options;
		$this->nondefaults = $nondefaults;
		$this->context = $context;
	}

	/**
	 * Outputs the results.
	 * @return string
	 */
	abstract protected function output();

	/// Processes messages before paging is done.
	abstract protected function preinit();

	/// Processes messages after paging is done.
	abstract protected function postinit();

	/**
	 * Executes the task with given options and outputs the results.
	 * @return string Partial or full html.
	 * @see plainOutput()
	 */
	final public function execute() {
		$this->preinit();
		$this->doPaging();
		$this->postinit();

		return $this->output();
	}

	/**
	 * Takes a slice of messages according to limit and offset given
	 * in option at initialisation time. Calls the callback to provide
	 * information how much messages there is.
	 */
	protected function doPaging() {
		$total = count( $this->collection );
		$offsets = $this->collection->slice(
			$this->options['offset'],
			$this->options['limit']
		);
		$left = count( $this->collection );

		$this->offsets = array(
			'backwardsOffset' => $offsets[0],
			'forwardsOffset' => $offsets[1],
			'start' => $offsets[2],
			'count' => $left,
			'total' => $total,
		);
	}

	/**
	 * Determine whether this user can use this task.
	 * Override this method if the task depends on user rights.
	 * @param User $user
	 * @return string
	 */
	public function isAllowedFor( User $user ) {
		return true;
	}
}

/**
 * Provides essentially free-form filtering access via tasks.
 * This essentially makes all other tasks redundant, and once
 * TUX is finished and everything is using WebAPI we can get
 * rid of these.
 * @since 2012-12-12
 */
class CustomFilteredMessagesTask extends TranslateTask {
	protected $id = 'custom';

	protected function preinit() {
	}

	protected function postinit() {
	}

	protected function doPaging() {
	}

	protected function output() {
		$table = new TuxMessageTable( $this->context, $this->group, $this->options['language'] );

		return $table->fullTable();
	}
}

/**
 * Lists all non-optional messages with translations if any.
 */
class ViewMessagesTask extends TranslateTask {
	protected $id = 'view';

	protected function preinit() {
		$code = $this->options['language'];
		$this->collection = $this->group->initCollection( $code );
		$this->collection->filter( 'ignored' );
		$this->collection->filter( 'optional' );
	}

	protected function postinit() {
		$this->collection->loadTranslations();
	}

	protected function output() {
		$table = MessageTable::newFromContext( $this->context, $this->collection, $this->group );

		return $table->fullTable( $this->offsets, $this->nondefaults );
	}
}

/**
 * Basic task class for review mode.
 */
class ReviewMessagesTask extends ViewMessagesTask {
	protected $id = 'review';

	protected function preinit() {
		$code = $this->options['language'];
		$this->collection = $this->group->initCollection( $code );
		$this->collection->filter( 'ignored' );
	}

	protected function output() {
		$table = MessageTable::newFromContext( $this->context, $this->collection, $this->group );
		$table->setReviewMode();

		return $table->fullTable( $this->offsets, $this->nondefaults );
	}
}

/**
 * Lists untranslated non-optional messages. This is often good default
 * task when translating.
 */
class ViewUntranslatedTask extends ViewMessagesTask {
	protected $id = 'untranslated';

	protected function preinit() {
		$code = $this->options['language'];
		$this->collection = $this->group->initCollection( $code );
		$this->collection->filter( 'ignored' );
		$this->collection->filter( 'optional' );
		$this->collection->filter( 'translated' );
	}
}

/**
 * Lists optional messages.
 */
class ViewOptionalTask extends ViewMessagesTask {
	protected $id = 'optional';

	protected function preinit() {
		$code = $this->options['language'];
		$this->collection = $this->group->initCollection( $code );
		$this->collection->filter( 'ignored' );
		$this->collection->filter( 'optional', false );
	}
}

/**
 * Lists all translations for reviewing.
 */
class ReviewAllMessagesTask extends ReviewMessagesTask {
	protected $id = 'reviewall';

	protected function preinit() {
		parent::preinit();
		$this->collection->filter( 'ignored' );
		$this->collection->filter( 'hastranslation', false );
	}
}

/**
 * Lists all translations the user can accept.
 */
class AcceptQueueMessagesTask extends ReviewMessagesTask {
	protected $id = 'acceptqueue';

	protected function preinit() {
		$user = $this->context->getUser();
		parent::preinit();
		$this->collection->filter( 'ignored' );
		$this->collection->filter( 'hastranslation', false );
		$this->collection->filter( 'fuzzy' );
		$this->collection->filter( 'reviewer', true, $user->getId() );
		$this->collection->filter( 'last-translator', true, $user->getId() );
	}

	public function isAllowedFor( User $user ) {
		return $user->isAllowed( 'translate-messagereview' );
	}
}

/**
 * Exports messages to their native format with embedded textarea.
 */
class ExportMessagesTask extends ViewMessagesTask {
	protected $id = 'export';

	protected function preinit() {
		$code = $this->options['language'];
		$this->collection = $this->group->initCollection( $code );
		// Don't export ignored, unless it is the source language
		// or message documentation
		global $wgTranslateDocumentationLanguageCode;
		if ( $code !== $wgTranslateDocumentationLanguageCode
			&& $code !== $this->group->getSourceLanguage()
		) {
			$this->collection->filter( 'ignored' );
		}
	}

	// No paging should be done.
	protected function doPaging() {
	}

	public function output() {
		return Html::element(
			'textarea',
			array( 'id' => 'wpTextbox1', 'rows' => '50' ),
			$this->group->getFFS()->writeIntoVariable( $this->collection )
		);
	}
}

/**
 * Exports messages to their native format as whole page.
 */
class ExportToFileMessagesTask extends ExportMessagesTask {
	protected $id = 'export-to-file';

	public function plainOutput() {
		return true;
	}

	public function output() {
		if ( !$this->group instanceof FileBasedMessageGroup ) {
			return 'Not supported';
		}

		$ffs = $this->group->getFFS();
		$data = $ffs->writeIntoVariable( $this->collection );

		$filename = basename( $this->group->getSourceFilePath( $this->collection->getLanguage() ) );
		header( "Content-Disposition: attachment; filename=\"$filename\"" );

		return $data;
	}
}

/**
 * Exports messages as special Gettext format that is suitable for off-line
 * translation with tools that support Gettext. These files can later be
 * imported back to the wiki.
 */
class ExportAsPoMessagesTask extends ExportMessagesTask {
	protected $id = 'export-as-po';

	public function plainOutput() {
		return true;
	}

	public function output() {
		if ( MessageGroups::isDynamic( $this->group ) ) {
			return 'Not supported';
		}

		$ffs = null;
		if ( $this->group instanceof FileBasedMessageGroup ) {
			$ffs = $this->group->getFFS();
		}

		if ( !$ffs instanceof GettextFFS ) {
			$group = FileBasedMessageGroup::newFromMessageGroup( $this->group );
			$ffs = new GettextFFS( $group );
		}

		$ffs->setOfflineMode( 'true' );

		$code = $this->options['language'];
		$id = $this->group->getID();
		$filename = "${id}_$code.po";
		header( "Content-Disposition: attachment; filename=\"$filename\"" );

		return $ffs->writeIntoVariable( $this->collection );
	}
}

/**
 * Collection of functions to get tasks.
 */
class TranslateTasks {
	/**
	 * Return list of available tasks.
	 * @param bool $pageTranslation Whether this group is page translation group.
	 * @todo Make the above parameter a group and check its class?
	 * @return string[] Task identifiers.
	 */
	public static function getTasks( $pageTranslation = false ) {
		global $wgTranslateTasks;

		// Tasks not to be available in page translation.
		$filterTasks = array(
			'optional',
			'export-to-file',
		);

		$allTasks = array_keys( $wgTranslateTasks );

		if ( $pageTranslation ) {
			$allTasks = array_diff( $allTasks, $filterTasks );
		}

		return $allTasks;
	}

	/**
	 * Get task by id.
	 * @param string $id Unique task identifier.
	 * @return TranslateTask|null Null if no such task.
	 */
	public static function getTask( $id ) {
		global $wgTranslateTasks;

		if ( array_key_exists( $id, $wgTranslateTasks ) ) {
			if ( is_callable( $wgTranslateTasks[$id] ) ) {
				return call_user_func( $wgTranslateTasks[$id], $id );
			}

			return new $wgTranslateTasks[$id];
		}

		return null;
	}
}