Passed
Push — master ( 364259...237be9 )
by Paul
04:18
created

Translator::getTranslations()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 6
ccs 4
cts 5
cp 0.8
crap 2.032
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules;
4
5
use Exception;
6
use GeminiLabs\SiteReviews\Application;
7
use GeminiLabs\SiteReviews\Database\OptionManager;
8
use GeminiLabs\SiteReviews\Modules\Html;
9
use Sepia\PoParser\Parser;
10
11
class Translator
12
{
13
	const SEARCH_THRESHOLD = 3;
14
15
	/**
16
	 * @var array
17
	 */
18
	protected $entries;
19
20
	/**
21
	 * @var array
22
	 */
23
	protected $results;
24
25
	/**
26
	 * Returns all saved custom translations with translation context
27
	 * @return array
28
	 */
29
	public function all()
30
	{
31
		$translations = $this->getTranslations();
32
		$entries = $this->filter( $translations, $this->entries() )->results();
33
		array_walk( $translations, function( &$entry ) use( $entries ) {
34
			$entry['desc'] = array_key_exists( $entry['id'], $entries )
35
				? $this->getEntryString( $entries[$entry['id']], 'msgctxt' )
36
				: '';
37
		});
38
		return $translations;
39
	}
40
41
	/**
42
	 * @return array
43
	 */
44
	public function entries()
45
	{
46
		if( !isset( $this->entries )) {
47
			try {
48
				$potFile = glsr()->path( glsr()->languages.'/'.Application::ID.'.pot' );
49
				$entries = $this->normalize( Parser::parseFile( $potFile )->getEntries() );
50
				foreach( $entries as $key => $entry ) {
51
					$this->entries[html_entity_decode( $key, ENT_COMPAT, 'UTF-8' )] = $entry;
52
				}
53
			}
54
			catch( Exception $e ) {
55
				glsr_log()->error( $e->getMessage() );
56
			}
57
		}
58
		return $this->entries;
59
	}
60
61
	/**
62
	 * @param null|array $entriesToExclude
63
	 * @param null|array $entries
64
	 * @return static
65
	 */
66
	public function exclude( $entriesToExclude = null, $entries = null )
67
	{
68
		return $this->filter( $entriesToExclude, $entries, false );
69
	}
70
71
	/**
72
	 * @param null|array $filterWith
73
	 * @param null|array $entries
74
	 * @param bool $intersect
75
	 * @return static
76
	 */
77
	public function filter( $filterWith = null, $entries = null, $intersect = true )
78
	{
79
		if( !is_array( $entries )) {
80
			$entries = $this->results;
81
		}
82
		if( !is_array( $filterWith )) {
83
			$filterWith = $this->getTranslations();
84
		}
85
		$keys = array_flip( array_column( $filterWith, 'id' ));
86
		$this->results = $intersect
87
			? array_intersect_key( $entries, $keys )
88
			: array_diff_key( $entries, $keys );
89
		return $this;
90
	}
91
92
	/**
93
	 * @param string $translation
94
	 * @param string $text
95
	 * @param string $domain
96
	 * @return string
97
	 */
98 7
	public function filterGettext( $translation, $text, $domain )
99
	{
100 7
		return $this->translate( $translation, $domain, [
101 7
			'single' => $text,
102
		]);
103
	}
104
105
	/**
106
	 * @param string $translation
107
	 * @param string $text
108
	 * @param string $context
109
	 * @param string $domain
110
	 * @return string
111
	 */
112 1
	public function filterGettextWithContext( $translation, $text, $context, $domain )
113
	{
114 1
		return $this->translate( $translation, $domain, [
115 1
			'context' => $context,
116 1
			'single' => $text,
117
		]);
118
	}
119
120
	/**
121
	 * @param string $translation
122
	 * @param string $single
123
	 * @param string $plural
124
	 * @param int $number
125
	 * @param string $domain
126
	 * @return string
127
	 */
128
	public function filterNgettext( $translation, $single, $plural, $number, $domain )
129
	{
130
		return $this->translate( $translation, $domain, [
131
			'number' => $number,
132
			'plural' => $plural,
133
			'single' => $single,
134
		]);
135
	}
136
137
	/**
138
	 * @param string $translation
139
	 * @param string $single
140
	 * @param string $plural
141
	 * @param int $number
142
	 * @param string $context
143
	 * @param string $domain
144
	 * @return string
145
	 */
146
	public function filterNgettextWithContext( $translation, $single, $plural, $number, $context, $domain )
147
	{
148
		return $this->translate( $translation, $domain, [
149
			'context' => $context,
150
			'number' => $number,
151
			'plural' => $plural,
152
			'single' => $single,
153
		]);
154
	}
155
156
	/**
157
	 * @param string $template
158
	 * @return string
159
	 */
160
	public function render( $template, array $entry )
161
	{
162
		$data = array_combine(
163
			array_map( function( $key ) { return 'data.'.$key; }, array_keys( $entry )),
164
			$entry
165
		);
166
		ob_start();
167
		glsr( Html::class )->renderTemplate( 'translations/'.$template, $data );
168
		return ob_get_clean();
169
	}
170
171
	/**
172
	 * Returns a rendered string of all saved custom translations with translation context
173
	 * @return string
174
	 */
175
	public function renderAll()
176
	{
177
		$rendered = '';
178
		foreach( $this->all() as $index => $entry ) {
179
			$entry['index'] = $index;
180
			$entry['prefix'] = OptionManager::databaseKey();
181
			$rendered .= $this->render( $entry['type'], $entry );
182
		}
183
		return $rendered;
184
	}
185
186
	/**
187
	 * @param bool $resetAfterRender
188
	 * @return string
189
	 */
190
	public function renderResults( $resetAfterRender = true )
191
	{
192
		$rendered = '';
193
		foreach( $this->results as $id => $entry ) {
194
			$data = [
195
				'desc' => $this->getEntryString( $entry, 'msgctxt' ),
196
				'id' => $id,
197
				'p1' => $this->getEntryString( $entry, 'msgid_plural' ),
198
				's1' => $this->getEntryString( $entry, 'msgid' ),
199
			];
200
			$text = !empty( $data['p1'] )
201
				? sprintf( '%s | %s', $data['s1'], $data['p1'] )
202
				: $data['s1'];
203
			$rendered .= $this->render( 'result', [
204
				'entry' => wp_json_encode( $data ),
205
				'text' => wp_strip_all_tags( $text ),
206
			]);
207
		}
208
		if( $resetAfterRender ) {
209
			$this->reset();
210
		}
211
		return $rendered;
212
	}
213
214
	/**
215
	 * @return void
216
	 */
217
	public function reset()
218
	{
219
		$this->results = [];
220
	}
221
222
	/**
223
	 * @return array
224
	 */
225
	public function results()
226
	{
227
		$results = $this->results;
228
		$this->reset();
229
		return $results;
230
	}
231
232
	/**
233
	 * @param string $needle
234
	 * @return static
235
	 */
236
	public function search( $needle = '' )
237
	{
238
		$this->reset();
239
		$needle = trim( strtolower( $needle ));
240
		foreach( $this->entries() as $key => $entry ) {
241
			$single = strtolower( $this->getEntryString( $entry, 'msgid' ));
242
			$plural = strtolower( $this->getEntryString( $entry, 'msgid_plural' ));
243
			if( strlen( $needle ) < static::SEARCH_THRESHOLD ) {
244
				if( in_array( $needle, [$single, $plural] )) {
245
					$this->results[$key] = $entry;
246
				}
247
			}
248
			else if( strpos( sprintf( '%s %s', $single, $plural ), $needle ) !== false ) {
249
				$this->results[$key] = $entry;
250
			}
251
		}
252
		return $this;
253
	}
254
255
	/**
256
	 * @param string $original
257
	 * @param string $domain
258
	 * @return string
259
	 */
260 7
	public function translate( $original, $domain, array $args )
261
	{
262 7
		if( $domain != Application::ID ) {
263 7
			return $original;
264
		}
265 7
		$args = $this->normalizeTranslationArgs( $args );
266 7
		$strings = $this->getTranslationStrings( $args['single'], $args['plural'] );
267 7
		if( empty( $strings )) {
268 7
			return $original;
269
		}
270
		$string = current( $strings );
271
		return $string['type'] == 'plural'
272
			? $this->translatePlural( $domain, $string, $args )
273
			: $this->translateSingle( $domain, $string, $args );
274
	}
275
276
	/**
277
	 * @param string $key
278
	 * @return string
279
	 */
280
	protected function getEntryString( array $entry, $key )
281
	{
282
		return isset( $entry[$key] )
283
			? implode( '', (array) $entry[$key] )
284
			: '';
285
	}
286
287
	/**
288
	 * @return array
289
	 */
290 7
	protected function getTranslations()
291
	{
292 7
		$settings = glsr( OptionManager::class )->get( 'settings' );
293 7
		return isset( $settings['translations'] )
294
			? $this->normalizeSettings( (array) $settings['translations'] )
295 7
			: [];
296
	}
297
298
	/**
299
	 * @param string $single
300
	 * @param string $plural
301
	 * @return array
302
	 */
303
	protected function getTranslationStrings( $single, $plural )
304
	{
305 7
		return array_filter( $this->getTranslations(), function( $string ) use( $single, $plural ) {
306
			return $string['s1'] == html_entity_decode( $single, ENT_COMPAT, 'UTF-8' )
307
				&& $string['p1'] == html_entity_decode( $plural, ENT_COMPAT, 'UTF-8' );
308 7
		});
309
	}
310
311
	/**
312
	 * @return array
313
	 */
314
	protected function normalize( array $entries )
315
	{
316
		$keys = [
317
			'msgctxt', 'msgid', 'msgid_plural', 'msgstr', 'msgstr[0]', 'msgstr[1]',
318
		];
319
		array_walk( $entries, function( &$entry ) use( $keys ) {
320
			foreach( $keys as $key ) {
321
				$entry = $this->normalizeEntryString( $entry, $key );
322
			}
323
		});
324
		return $entries;
325
	}
326
327
	/**
328
	 * @param string $key
329
	 * @return array
330
	 */
331
	protected function normalizeEntryString( array $entry, $key )
332
	{
333
		if( isset( $entry[$key] )) {
334
			$entry[$key] = $this->getEntryString( $entry, $key );
335
		}
336
		return $entry;
337
	}
338
339
	/**
340
	 * @return array
341
	 */
342
	protected function normalizeSettings( array $strings )
343
	{
344
		$defaultString = array_fill_keys( ['id', 's1', 's2', 'p1', 'p2'], '' );
345
		$strings = array_filter( $strings, 'is_array' );
346
		foreach( $strings as &$string ) {
347
			$string['type'] = isset( $string['p1'] ) ? 'plural' : 'single';
348
			$string = wp_parse_args( $string, $defaultString );
349
		}
350
		return array_filter( $strings, function( $string ) {
351
			return !empty( $string['id'] );
352
		});
353
	}
354
355
	/**
356
	 * @return array
357
	 */
358 7
	protected function normalizeTranslationArgs( array $args )
359
	{
360
		$defaults = [
361 7
			'context' => '',
362
			'number' => 1,
363
			'plural' => '',
364
			'single' => '',
365
		];
366 7
		return shortcode_atts( $defaults, $args );
367
	}
368
369
	/**
370
	 * @param string $domain
371
	 * @return string
372
	 */
373
	protected function translatePlural( $domain, array $string, array $args )
374
	{
375
		if( !empty( $string['p2'] )) {
376
			$args['plural'] = $string['p2'];
377
		}
378
		return get_translations_for_domain( $domain )->translate_plural(
379
			$args['single'],
380
			$args['plural'],
381
			$args['number'],
382
			$args['context']
383
		);
384
	}
385
386
	/**
387
	 * @param string $domain
388
	 * @return string
389
	 */
390
	protected function translateSingle( $domain, array $string, array $args )
391
	{
392
		if( !empty( $string['s2'] )) {
393
			$args['single'] = $string['s2'];
394
		}
395
		return get_translations_for_domain( $domain )->translate(
396
			$args['single'],
397
			$args['context']
398
		);
399
	}
400
}
401