Passed
Push — master ( 43cb65...18d76b )
by Paul
04:03
created

Translation::normalizeEntryString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 2
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules;
4
5
use Exception;
6
use GeminiLabs\Sepia\PoParser\Parser;
7
use GeminiLabs\SiteReviews\Application;
8
use GeminiLabs\SiteReviews\Database\OptionManager;
9
use GeminiLabs\SiteReviews\Modules\Html\Template;
10
11
class Translation
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->translations();
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
			$potFile = glsr()->path( glsr()->languages.'/'.Application::ID.'.pot' );
48
			$entries = $this->extractEntriesFromPotFile( $potFile );
49
			$entries = apply_filters( 'site-reviews/translation/entries', $entries );
50
			$this->entries = $entries;
51
		}
52
		return $this->entries;
53
	}
54
55
	/**
56
	 * @param null|array $entriesToExclude
57
	 * @param null|array $entries
58
	 * @return static
59
	 */
60
	public function exclude( $entriesToExclude = null, $entries = null )
61
	{
62
		return $this->filter( $entriesToExclude, $entries, false );
63
	}
64
65
	/**
66
	 * @param string $potFile
67
	 * @return array
68
	 */
69
	public function extractEntriesFromPotFile( $potFile, array $entries = [] )
70
	{
71
		try {
72
			$potEntries = $this->normalize( Parser::parseFile( $potFile )->getEntries() );
73
			foreach( $potEntries as $key => $entry ) {
74
				$entries[html_entity_decode( $key, ENT_COMPAT, 'UTF-8' )] = $entry;
75
			}
76
		}
77
		catch( Exception $e ) {
78
			glsr_log()->error( $e->getMessage() );
79
		}
80
		return $entries;
81
	}
82
83
	/**
84
	 * @param null|array $filterWith
85
	 * @param null|array $entries
86
	 * @param bool $intersect
87
	 * @return static
88
	 */
89
	public function filter( $filterWith = null, $entries = null, $intersect = true )
90
	{
91
		if( !is_array( $entries )) {
92
			$entries = $this->results;
93
		}
94
		if( !is_array( $filterWith )) {
95
			$filterWith = $this->translations();
96
		}
97
		$keys = array_flip( glsr_array_column( $filterWith, 'id' ));
98
		$this->results = $intersect
99
			? array_intersect_key( $entries, $keys )
100
			: array_diff_key( $entries, $keys );
101
		return $this;
102
	}
103
104
	/**
105
	 * @param string $template
106
	 * @return string
107
	 */
108
	public function render( $template, array $entry )
109
	{
110
		$data = array_combine(
111
			array_map( function( $key ) { return 'data.'.$key; }, array_keys( $entry )),
112
			$entry
113
		);
114
		$data['data.class'] = $data['data.error'] = '';
115
		if( array_search( $entry['s1'], glsr_array_column( $this->entries(), 'msgid' )) === false ) {
116
			$data['data.class'] = 'is-invalid';
117
			$data['data.error'] = __( 'This custom translation is no longer valid as the original text has been changed or removed.', 'site-reviews' );
118
		}
119
		return glsr( Template::class )->build( 'partials/translations/'.$template, [
120
			'context' => $data,
121
		]);
122
	}
123
124
	/**
125
	 * Returns a rendered string of all saved custom translations with translation context
126
	 * @return string
127
	 */
128
	public function renderAll()
129
	{
130
		$rendered = '';
131
		foreach( $this->all() as $index => $entry ) {
132
			$entry['index'] = $index;
133
			$entry['prefix'] = OptionManager::databaseKey();
134
			$rendered .= $this->render( $entry['type'], $entry );
135
		}
136
		return $rendered;
137
	}
138
139
	/**
140
	 * @param bool $resetAfterRender
141
	 * @return string
142
	 */
143
	public function renderResults( $resetAfterRender = true )
144
	{
145
		$rendered = '';
146
		foreach( $this->results as $id => $entry ) {
147
			$data = [
148
				'desc' => $this->getEntryString( $entry, 'msgctxt' ),
149
				'id' => $id,
150
				'p1' => $this->getEntryString( $entry, 'msgid_plural' ),
151
				's1' => $this->getEntryString( $entry, 'msgid' ),
152
			];
153
			$text = !empty( $data['p1'] )
154
				? sprintf( '%s | %s', $data['s1'], $data['p1'] )
155
				: $data['s1'];
156
			$rendered .= $this->render( 'result', [
157
				'entry' => json_encode( $data, JSON_HEX_APOS|JSON_HEX_QUOT|JSON_HEX_TAG|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE ),
158
				'text' => wp_strip_all_tags( $text ),
159
			]);
160
		}
161
		if( $resetAfterRender ) {
162
			$this->reset();
163
		}
164
		return $rendered;
165
	}
166
167
	/**
168
	 * @return void
169
	 */
170
	public function reset()
171
	{
172
		$this->results = [];
173
	}
174
175
	/**
176
	 * @return array
177
	 */
178
	public function results()
179
	{
180
		$results = $this->results;
181
		$this->reset();
182
		return $results;
183
	}
184
185
	/**
186
	 * @param string $needle
187
	 * @return static
188
	 */
189
	public function search( $needle = '' )
190
	{
191
		$this->reset();
192
		$needle = trim( strtolower( $needle ));
193
		foreach( $this->entries() as $key => $entry ) {
194
			$single = strtolower( $this->getEntryString( $entry, 'msgid' ));
195
			$plural = strtolower( $this->getEntryString( $entry, 'msgid_plural' ));
196
			if( strlen( $needle ) < static::SEARCH_THRESHOLD ) {
197
				if( in_array( $needle, [$single, $plural] )) {
198
					$this->results[$key] = $entry;
199
				}
200
			}
201
			else if( strpos( sprintf( '%s %s', $single, $plural ), $needle ) !== false ) {
202
				$this->results[$key] = $entry;
203
			}
204
		}
205
		return $this;
206
	}
207
208
	/**
209
	 * Store the translations to avoid unnecessary loops
210
	 * @return array
211
	 */
212 7
	public function translations()
213
	{
214 7
		static $translations;
215 7
		if( empty( $translations )) {
216 7
			$settings = glsr( OptionManager::class )->get( 'settings' );
217 7
			$translations = isset( $settings['strings'] )
218
				? $this->normalizeSettings( (array) $settings['strings'] )
219 7
				: [];
220
		}
221 7
		return $translations;
222
	}
223
224
	/**
225
	 * @param string $key
226
	 * @return string
227
	 */
228
	protected function getEntryString( array $entry, $key )
229
	{
230
		return isset( $entry[$key] )
231
			? implode( '', (array) $entry[$key] )
232
			: '';
233
	}
234
235
	/**
236
	 * @return array
237
	 */
238
	protected function normalize( array $entries )
239
	{
240
		$keys = [
241
			'msgctxt', 'msgid', 'msgid_plural', 'msgstr', 'msgstr[0]', 'msgstr[1]',
242
		];
243
		array_walk( $entries, function( &$entry ) use( $keys ) {
244
			foreach( $keys as $key ) {
245
				if( is_array( $entry )) {
246
					$entry = $this->normalizeEntryString( $entry, $key );
247
					continue;
248
				}
249
				glsr_log()->once( '[Translation\normalize()] $entry is not an array', $entry );
250
			}
251
		});
252
		return $entries;
253
	}
254
255
	/**
256
	 * @param string $key
257
	 * @return array
258
	 */
259
	protected function normalizeEntryString( array $entry, $key )
260
	{
261
		if( isset( $entry[$key] )) {
262
			$entry[$key] = $this->getEntryString( $entry, $key );
263
		}
264
		return $entry;
265
	}
266
267
	/**
268
	 * @return array
269
	 */
270
	protected function normalizeSettings( array $strings )
271
	{
272
		$defaultString = array_fill_keys( ['id', 's1', 's2', 'p1', 'p2'], '' );
273
		$strings = array_filter( $strings, 'is_array' );
274
		foreach( $strings as &$string ) {
275
			$string['type'] = isset( $string['p1'] ) ? 'plural' : 'single';
276
			$string = wp_parse_args( $string, $defaultString );
277
		}
278
		return array_filter( $strings, function( $string ) {
279
			return !empty( $string['id'] );
280
		});
281
	}
282
}
283