Passed
Push — master ( d26ef1...d85e7c )
by Paul
04:14
created

Translator   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 388
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 388
ccs 0
cts 157
cp 0
rs 8.3206
c 0
b 0
f 0
wmc 51

24 Methods

Rating   Name   Duplication   Size   Complexity  
B renderResults() 0 22 4
A filter() 0 13 4
A render() 0 9 1
A reset() 0 3 1
A renderAll() 0 9 2
A all() 0 10 2
A filterNgettextWithContext() 0 7 1
B search() 0 17 5
A results() 0 5 1
A entries() 0 16 4
A filterNgettext() 0 6 1
A filterGettextWithContext() 0 5 1
A filterGettext() 0 4 1
A exclude() 0 3 1
A normalizeSettings() 0 10 3
A getTranslationStrings() 0 5 2
A normalizeEntryString() 0 6 2
A normalizeTranslationArgs() 0 9 1
A normalize() 0 11 2
A translateSingle() 0 8 2
A translatePlural() 0 10 2
A getTranslations() 0 6 2
A getEntryString() 0 5 2
A translate() 0 14 4

How to fix   Complexity   

Complex Class

Complex classes like Translator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Translator, and based on these observations, apply Extract Interface, too.

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
				$entries = $this->normalize(
49
					Parser::parseFile( glsr()->path( 'languages/site-reviews.pot' ))->getEntries()
50
				);
51
			}
52
			catch( Exception $e ) {
53
				glsr_log()->error( $e->getMessage() );
54
			}
55
			foreach( $entries as $key => $entry ) {
56
				$this->entries[html_entity_decode( $key, ENT_COMPAT, 'UTF-8' )] = $entry;
57
			}
58
		}
59
		return $this->entries;
60
	}
61
62
	/**
63
	 * @param null|array $entriesToExclude
64
	 * @param null|array $entries
65
	 * @return static
66
	 */
67
	public function exclude( $entriesToExclude = null, $entries = null )
68
	{
69
		return $this->filter( $entriesToExclude, $entries, false );
70
	}
71
72
	/**
73
	 * @param null|array $filterWith
74
	 * @param null|array $entries
75
	 * @param bool $intersect
76
	 * @return static
77
	 */
78
	public function filter( $filterWith = null, $entries = null, $intersect = true )
79
	{
80
		if( !is_array( $entries )) {
81
			$entries = $this->results;
82
		}
83
		if( !is_array( $filterWith )) {
84
			$filterWith = $this->getTranslations();
85
		}
86
		$keys = array_flip( array_column( $filterWith, 'id' ));
87
		$this->results = $intersect
88
			? array_intersect_key( $entries, $keys )
89
			: array_diff_key( $entries, $keys );
90
		return $this;
91
	}
92
93
	/**
94
	 * @param string $translation
95
	 * @param string $text
96
	 * @param string $domain
97
	 * @return string
98
	 */
99
	public function filterGettext( $translation, $text, $domain )
100
	{
101
		return $this->translate( $translation, $domain, [
102
			'single' => $text,
103
		]);
104
	}
105
106
	/**
107
	 * @param string $translation
108
	 * @param string $text
109
	 * @param string $context
110
	 * @param string $domain
111
	 * @return string
112
	 */
113
	public function filterGettextWithContext( $translation, $text, $context, $domain )
114
	{
115
		return $this->translate( $translation, $domain, [
116
			'context' => $context,
117
			'single' => $text,
118
		]);
119
	}
120
121
	/**
122
	 * @param string $translation
123
	 * @param string $single
124
	 * @param string $plural
125
	 * @param int $number
126
	 * @param string $domain
127
	 * @return string
128
	 */
129
	public function filterNgettext( $translation, $single, $plural, $number, $domain )
130
	{
131
		return $this->translate( $translation, $domain, [
132
			'number' => $number,
133
			'plural' => $plural,
134
			'single' => $single,
135
		]);
136
	}
137
138
	/**
139
	 * @param string $translation
140
	 * @param string $single
141
	 * @param string $plural
142
	 * @param int $number
143
	 * @param string $context
144
	 * @param string $domain
145
	 * @return string
146
	 */
147
	public function filterNgettextWithContext( $translation, $single, $plural, $number, $context, $domain )
148
	{
149
		return $this->translate( $translation, $domain, [
150
			'context' => $context,
151
			'number' => $number,
152
			'plural' => $plural,
153
			'single' => $single,
154
		]);
155
	}
156
157
	/**
158
	 * @param string $template
159
	 * @return string
160
	 */
161
	public function render( $template, array $entry )
162
	{
163
		$data = array_combine(
164
			array_map( function( $key ) { return 'data.'.$key; }, array_keys( $entry )),
165
			$entry
166
		);
167
		ob_start();
168
		glsr( Html::class )->renderTemplate( 'translations/'.$template, $data );
169
		return ob_get_clean();
170
	}
171
172
	/**
173
	 * Returns a rendered string of all saved custom translations with translation context
174
	 * @return string
175
	 */
176
	public function renderAll()
177
	{
178
		$rendered = '';
179
		foreach( $this->all() as $index => $entry ) {
180
			$entry['index'] = $index;
181
			$entry['prefix'] = OptionManager::databaseKey();
182
			$rendered .= $this->render( $entry['type'], $entry );
183
		}
184
		return $rendered;
185
	}
186
187
	/**
188
	 * @param bool $resetAfterRender
189
	 * @return string
190
	 */
191
	public function renderResults( $resetAfterRender = true )
192
	{
193
		$rendered = '';
194
		foreach( $this->results as $id => $entry ) {
195
			$data = [
196
				'desc' => $this->getEntryString( $entry, 'msgctxt' ),
197
				'id' => $id,
198
				'p1' => $this->getEntryString( $entry, 'msgid_plural' ),
199
				's1' => $this->getEntryString( $entry, 'msgid' ),
200
			];
201
			$text = !empty( $data['p1'] )
202
				? sprintf( '%s | %s', $data['s1'], $data['p1'] )
203
				: $data['s1'];
204
			$rendered .= $this->render( 'result', [
205
				'entry' => wp_json_encode( $data ),
206
				'text' => wp_strip_all_tags( $text ),
207
			]);
208
		}
209
		if( $resetAfterRender ) {
210
			$this->reset();
211
		}
212
		return $rendered;
213
	}
214
215
	/**
216
	 * @return void
217
	 */
218
	public function reset()
219
	{
220
		$this->results = [];
221
	}
222
223
	/**
224
	 * @return array
225
	 */
226
	public function results()
227
	{
228
		$results = $this->results;
229
		$this->reset();
230
		return $results;
231
	}
232
233
	/**
234
	 * @param string $needle
235
	 * @return static
236
	 */
237
	public function search( $needle = '' )
238
	{
239
		$this->reset();
240
		$needle = trim( strtolower( $needle ));
241
		foreach( $this->entries() as $key => $entry ) {
242
			$single = strtolower( $this->getEntryString( $entry, 'msgid' ));
243
			$plural = strtolower( $this->getEntryString( $entry, 'msgid_plural' ));
244
			if( strlen( $needle ) < static::SEARCH_THRESHOLD ) {
245
				if( in_array( $needle, [$single, $plural] )) {
246
					$this->results[$key] = $entry;
247
				}
248
			}
249
			else if( strpos( sprintf( '%s %s', $single, $plural ), $needle ) !== false ) {
250
				$this->results[$key] = $entry;
251
			}
252
		}
253
		return $this;
254
	}
255
256
	/**
257
	 * @param string $original
258
	 * @param string $domain
259
	 * @return string
260
	 */
261
	public function translate( $original, $domain, array $args )
262
	{
263
		if( $domain != Application::ID ) {
264
			return $original;
265
		}
266
		$args = $this->normalizeTranslationArgs( $args );
267
		$strings = $this->getTranslationStrings( $args['single'], $args['plural'] );
268
		if( empty( $strings )) {
269
			return $original;
270
		}
271
		$string = current( $strings );
272
		return $string['type'] == 'plural'
273
			? $this->translatePlural( $domain, $string, $args )
274
			: $this->translateSingle( $domain, $string, $args );
275
	}
276
277
	/**
278
	 * @param string $key
279
	 * @return string
280
	 */
281
	protected function getEntryString( array $entry, $key )
282
	{
283
		return isset( $entry[$key] )
284
			? implode( '', (array) $entry[$key] )
285
			: '';
286
	}
287
288
	/**
289
	 * @return array
290
	 */
291
	protected function getTranslations()
292
	{
293
		$settings = glsr( OptionManager::class )->get( 'settings' );
294
		return isset( $settings['translations'] )
295
			? $this->normalizeSettings( (array) $settings['translations'] )
296
			: [];
297
	}
298
299
	/**
300
	 * @param string $singleText
301
	 * @param string $pluralText
302
	 * @return array
303
	 */
304
	protected function getTranslationStrings( $singleText, $pluralText )
305
	{
306
		return array_filter( $this->getTranslations(), function( $string ) use( $args ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $args seems to be never defined.
Loading history...
Unused Code introduced by
The import $args is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
307
			return $string['s1'] == html_entity_decode( $singleText, ENT_COMPAT, 'UTF-8' )
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $singleText seems to be never defined.
Loading history...
308
				&& $string['p1'] == html_entity_decode( $pluralText, ENT_COMPAT, 'UTF-8' );
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pluralText seems to be never defined.
Loading history...
309
		});
310
	}
311
312
	/**
313
	 * @return array
314
	 */
315
	protected function normalize( array $entries )
316
	{
317
		$keys = [
318
			'msgctxt', 'msgid', 'msgid_plural', 'msgstr', 'msgstr[0]', 'msgstr[1]',
319
		];
320
		array_walk( $entries, function( &$entry ) use( $keys ) {
321
			foreach( $keys as $key ) {
322
				$entry = $this->normalizeEntryString( $entry, $key );
323
			}
324
		});
325
		return $entries;
326
	}
327
328
	/**
329
	 * @param string $key
330
	 * @return array
331
	 */
332
	protected function normalizeEntryString( array $entry, $key )
333
	{
334
		if( isset( $entry[$key] )) {
335
			$entry[$key] = $this->getEntryString( $entry, $key );
336
		}
337
		return $entry;
338
	}
339
340
	/**
341
	 * @return array
342
	 */
343
	protected function normalizeSettings( array $strings )
344
	{
345
		$defaultString = array_fill_keys( ['id', 's1', 's2', 'p1', 'p2'], '' );
346
		$strings = array_filter( $strings, 'is_array' );
347
		foreach( $strings as &$string ) {
348
			$string['type'] = isset( $string['p1'] ) ? 'plural' : 'single';
349
			$string = wp_parse_args( $string, $defaultString );
350
		}
351
		return array_filter( $strings, function( $string ) {
352
			return !empty( $string['id'] );
353
		});
354
	}
355
356
	/**
357
	 * @return array
358
	 */
359
	protected function normalizeTranslationArgs( array $args )
360
	{
361
		$defaults = [
362
			'context' => '',
363
			'number' => 1,
364
			'plural' => '',
365
			'single' => '',
366
		];
367
		return shortcode_atts( $defaults, $args );
368
	}
369
370
	/**
371
	 * @param string $domain
372
	 * @return string
373
	 */
374
	protected function translatePlural( $domain, array $string, array $args )
375
	{
376
		if( !empty( $string['p2'] )) {
377
			$args['plural'] = $string['p2'];
378
		}
379
		return get_translations_for_domain( $domain )->translate_plural(
380
			$args['single'],
381
			$args['plural'],
382
			$args['number'],
383
			$args['context']
384
		);
385
	}
386
387
	/**
388
	 * @param string $domain
389
	 * @return string
390
	 */
391
	protected function translateSingle( $domain, array $string, array $args )
392
	{
393
		if( !empty( $string['s2'] )) {
394
			$args['single'] = $string['s2'];
395
		}
396
		return get_translations_for_domain( $domain )->translate(
397
			$args['single'],
398
			$args['context']
399
		);
400
	}
401
}
402