Passed
Push — master ( d39aaa...dbec6e )
by Paul
04:28
created

Translator::results()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 5
ccs 0
cts 4
cp 0
crap 2
rs 10
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 2
	public function filterGettextWithContext( $translation, $text, $context, $domain )
113
	{
114 2
		return $this->translate( $translation, $domain, [
115 2
			'context' => $context,
116 2
			'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 1
	public function filterNgettext( $translation, $single, $plural, $number, $domain )
129
	{
130 1
		return $this->translate( $translation, $domain, [
131 1
			'number' => $number,
132 1
			'plural' => $plural,
133 1
			'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( 'partials/translations/'.$template, [
168
			'context' => $data,
169
		]);
170
		return ob_get_clean();
171
	}
172
173
	/**
174
	 * Returns a rendered string of all saved custom translations with translation context
175
	 * @return string
176
	 */
177
	public function renderAll()
178
	{
179
		$rendered = '';
180
		foreach( $this->all() as $index => $entry ) {
181
			$entry['index'] = $index;
182
			$entry['prefix'] = OptionManager::databaseKey();
183
			$rendered .= $this->render( $entry['type'], $entry );
184
		}
185
		return $rendered;
186
	}
187
188
	/**
189
	 * @param bool $resetAfterRender
190
	 * @return string
191
	 */
192
	public function renderResults( $resetAfterRender = true )
193
	{
194
		$rendered = '';
195
		foreach( $this->results as $id => $entry ) {
196
			$data = [
197
				'desc' => $this->getEntryString( $entry, 'msgctxt' ),
198
				'id' => $id,
199
				'p1' => $this->getEntryString( $entry, 'msgid_plural' ),
200
				's1' => $this->getEntryString( $entry, 'msgid' ),
201
			];
202
			$text = !empty( $data['p1'] )
203
				? sprintf( '%s | %s', $data['s1'], $data['p1'] )
204
				: $data['s1'];
205
			$rendered .= $this->render( 'result', [
206
				'entry' => wp_json_encode( $data ),
207
				'text' => wp_strip_all_tags( $text ),
208
			]);
209
		}
210
		if( $resetAfterRender ) {
211
			$this->reset();
212
		}
213
		return $rendered;
214
	}
215
216
	/**
217
	 * @return void
218
	 */
219
	public function reset()
220
	{
221
		$this->results = [];
222
	}
223
224
	/**
225
	 * @return array
226
	 */
227
	public function results()
228
	{
229
		$results = $this->results;
230
		$this->reset();
231
		return $results;
232
	}
233
234
	/**
235
	 * @param string $needle
236
	 * @return static
237
	 */
238
	public function search( $needle = '' )
239
	{
240
		$this->reset();
241
		$needle = trim( strtolower( $needle ));
242
		foreach( $this->entries() as $key => $entry ) {
243
			$single = strtolower( $this->getEntryString( $entry, 'msgid' ));
244
			$plural = strtolower( $this->getEntryString( $entry, 'msgid_plural' ));
245
			if( strlen( $needle ) < static::SEARCH_THRESHOLD ) {
246
				if( in_array( $needle, [$single, $plural] )) {
247
					$this->results[$key] = $entry;
248
				}
249
			}
250
			else if( strpos( sprintf( '%s %s', $single, $plural ), $needle ) !== false ) {
251
				$this->results[$key] = $entry;
252
			}
253
		}
254
		return $this;
255
	}
256
257
	/**
258
	 * @param string $original
259
	 * @param string $domain
260
	 * @return string
261
	 */
262 7
	public function translate( $original, $domain, array $args )
263
	{
264 7
		if( $domain != Application::ID ) {
265 7
			return $original;
266
		}
267 7
		$args = $this->normalizeTranslationArgs( $args );
268 7
		$strings = $this->getTranslationStrings( $args['single'], $args['plural'] );
269 7
		if( empty( $strings )) {
270 7
			return $original;
271
		}
272
		$string = current( $strings );
273
		return $string['type'] == 'plural'
274
			? $this->translatePlural( $domain, $string, $args )
275
			: $this->translateSingle( $domain, $string, $args );
276
	}
277
278
	/**
279
	 * @param string $key
280
	 * @return string
281
	 */
282
	protected function getEntryString( array $entry, $key )
283
	{
284
		return isset( $entry[$key] )
285
			? implode( '', (array) $entry[$key] )
286
			: '';
287
	}
288
289
	/**
290
	 * Store the translations to avoid unnecessary loops
291
	 * @return array
292
	 */
293 7
	protected function getTranslations()
294
	{
295 7
		static $translations;
296 7
		if( empty( $translations )) {
297 7
			$settings = glsr( OptionManager::class )->get( 'settings' );
298 7
			$translations = isset( $settings['strings'] )
299
				? $this->normalizeSettings( (array) $settings['strings'] )
300 7
				: [];
301
		}
302 7
		return $translations;
303
	}
304
305
	/**
306
	 * @param string $single
307
	 * @param string $plural
308
	 * @return array
309
	 */
310
	protected function getTranslationStrings( $single, $plural )
311
	{
312 7
		return array_filter( $this->getTranslations(), function( $string ) use( $single, $plural ) {
313
			return $string['s1'] == html_entity_decode( $single, ENT_COMPAT, 'UTF-8' )
314
				&& $string['p1'] == html_entity_decode( $plural, ENT_COMPAT, 'UTF-8' );
315 7
		});
316
	}
317
318
	/**
319
	 * @return array
320
	 */
321
	protected function normalize( array $entries )
322
	{
323
		$keys = [
324
			'msgctxt', 'msgid', 'msgid_plural', 'msgstr', 'msgstr[0]', 'msgstr[1]',
325
		];
326
		array_walk( $entries, function( &$entry ) use( $keys ) {
327
			foreach( $keys as $key ) {
328
				$entry = $this->normalizeEntryString( $entry, $key );
329
			}
330
		});
331
		return $entries;
332
	}
333
334
	/**
335
	 * @param string $key
336
	 * @return array
337
	 */
338
	protected function normalizeEntryString( array $entry, $key )
339
	{
340
		if( isset( $entry[$key] )) {
341
			$entry[$key] = $this->getEntryString( $entry, $key );
342
		}
343
		return $entry;
344
	}
345
346
	/**
347
	 * @return array
348
	 */
349
	protected function normalizeSettings( array $strings )
350
	{
351
		$defaultString = array_fill_keys( ['id', 's1', 's2', 'p1', 'p2'], '' );
352
		$strings = array_filter( $strings, 'is_array' );
353
		foreach( $strings as &$string ) {
354
			$string['type'] = isset( $string['p1'] ) ? 'plural' : 'single';
355
			$string = wp_parse_args( $string, $defaultString );
356
		}
357
		return array_filter( $strings, function( $string ) {
358
			return !empty( $string['id'] );
359
		});
360
	}
361
362
	/**
363
	 * @return array
364
	 */
365 7
	protected function normalizeTranslationArgs( array $args )
366
	{
367
		$defaults = [
368 7
			'context' => '',
369
			'number' => 1,
370
			'plural' => '',
371
			'single' => '',
372
		];
373 7
		return shortcode_atts( $defaults, $args );
374
	}
375
376
	/**
377
	 * @param string $domain
378
	 * @return string
379
	 */
380
	protected function translatePlural( $domain, array $string, array $args )
381
	{
382
		if( !empty( $string['p2'] )) {
383
			$args['plural'] = $string['p2'];
384
		}
385
		return get_translations_for_domain( $domain )->translate_plural(
386
			$args['single'],
387
			$args['plural'],
388
			$args['number'],
389
			$args['context']
390
		);
391
	}
392
393
	/**
394
	 * @param string $domain
395
	 * @return string
396
	 */
397
	protected function translateSingle( $domain, array $string, array $args )
398
	{
399
		if( !empty( $string['s2'] )) {
400
			$args['single'] = $string['s2'];
401
		}
402
		return get_translations_for_domain( $domain )->translate(
403
			$args['single'],
404
			$args['context']
405
		);
406
	}
407
}
408