Passed
Push — master ( a42385...9b46a7 )
by Paul
04:14
created

Translation::search()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 5
nop 1
dl 0
loc 17
ccs 0
cts 12
cp 0
crap 30
rs 9.6111
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\Template;
9
use Sepia\PoParser\Parser;
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
			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->translations();
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 $template
94
	 * @return string
95
	 */
96
	public function render( $template, array $entry )
97
	{
98
		$data = array_combine(
99
			array_map( function( $key ) { return 'data.'.$key; }, array_keys( $entry )),
100
			$entry
101
		);
102
		$data['data.class'] = $data['data.error'] = '';
103
		if( array_search( $entry['s1'], array_column( $this->entries(), 'msgid' )) === false ) {
104
			$data['data.class'] = 'is-invalid';
105
			$data['data.error'] = __( 'This custom translation is no longer valid as the original text has been changed or removed.', 'site-reviews' );
106
		}
107
		return glsr( Template::class )->build( 'partials/translations/'.$template, [
108
			'context' => $data,
109
		]);
110
	}
111
112
	/**
113
	 * Returns a rendered string of all saved custom translations with translation context
114
	 * @return string
115
	 */
116
	public function renderAll()
117
	{
118
		$rendered = '';
119
		foreach( $this->all() as $index => $entry ) {
120
			$entry['index'] = $index;
121
			$entry['prefix'] = OptionManager::databaseKey();
122
			$rendered .= $this->render( $entry['type'], $entry );
123
		}
124
		return $rendered;
125
	}
126
127
	/**
128
	 * @param bool $resetAfterRender
129
	 * @return string
130
	 */
131
	public function renderResults( $resetAfterRender = true )
132
	{
133
		$rendered = '';
134
		foreach( $this->results as $id => $entry ) {
135
			$data = [
136
				'desc' => $this->getEntryString( $entry, 'msgctxt' ),
137
				'id' => $id,
138
				'p1' => $this->getEntryString( $entry, 'msgid_plural' ),
139
				's1' => $this->getEntryString( $entry, 'msgid' ),
140
			];
141
			$text = !empty( $data['p1'] )
142
				? sprintf( '%s | %s', $data['s1'], $data['p1'] )
143
				: $data['s1'];
144
			$rendered .= $this->render( 'result', [
145
				'entry' => json_encode( $data, JSON_HEX_APOS|JSON_HEX_QUOT|JSON_HEX_TAG|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE ),
146
				'text' => wp_strip_all_tags( $text ),
147
			]);
148
		}
149
		if( $resetAfterRender ) {
150
			$this->reset();
151
		}
152
		return $rendered;
153
	}
154
155
	/**
156
	 * @return void
157
	 */
158
	public function reset()
159
	{
160
		$this->results = [];
161
	}
162
163
	/**
164
	 * @return array
165
	 */
166
	public function results()
167
	{
168
		$results = $this->results;
169
		$this->reset();
170
		return $results;
171
	}
172
173
	/**
174
	 * @param string $needle
175
	 * @return static
176
	 */
177
	public function search( $needle = '' )
178
	{
179
		$this->reset();
180
		$needle = trim( strtolower( $needle ));
181
		foreach( $this->entries() as $key => $entry ) {
182
			$single = strtolower( $this->getEntryString( $entry, 'msgid' ));
183
			$plural = strtolower( $this->getEntryString( $entry, 'msgid_plural' ));
184
			if( strlen( $needle ) < static::SEARCH_THRESHOLD ) {
185
				if( in_array( $needle, [$single, $plural] )) {
186
					$this->results[$key] = $entry;
187
				}
188
			}
189
			else if( strpos( sprintf( '%s %s', $single, $plural ), $needle ) !== false ) {
190
				$this->results[$key] = $entry;
191
			}
192
		}
193
		return $this;
194
	}
195
196
	/**
197
	 * Store the translations to avoid unnecessary loops
198
	 * @return array
199
	 */
200 7
	public function translations()
201
	{
202 7
		static $translations;
203 7
		if( empty( $translations )) {
204 7
			$settings = glsr( OptionManager::class )->get( 'settings' );
205 7
			$translations = isset( $settings['strings'] )
206
				? $this->normalizeSettings( (array) $settings['strings'] )
207 7
				: [];
208
		}
209 7
		return $translations;
210
	}
211
212
	/**
213
	 * @param string $key
214
	 * @return string
215
	 */
216
	protected function getEntryString( array $entry, $key )
217
	{
218
		return isset( $entry[$key] )
219
			? implode( '', (array) $entry[$key] )
220
			: '';
221
	}
222
223
	/**
224
	 * @return array
225
	 */
226
	protected function normalize( array $entries )
227
	{
228
		$keys = [
229
			'msgctxt', 'msgid', 'msgid_plural', 'msgstr', 'msgstr[0]', 'msgstr[1]',
230
		];
231
		array_walk( $entries, function( &$entry ) use( $keys ) {
232
			foreach( $keys as $key ) {
233
				$entry = $this->normalizeEntryString( $entry, $key );
234
			}
235
		});
236
		return $entries;
237
	}
238
239
	/**
240
	 * @param string $key
241
	 * @return array
242
	 */
243
	protected function normalizeEntryString( array $entry, $key )
244
	{
245
		if( isset( $entry[$key] )) {
246
			$entry[$key] = $this->getEntryString( $entry, $key );
247
		}
248
		return $entry;
249
	}
250
251
	/**
252
	 * @return array
253
	 */
254
	protected function normalizeSettings( array $strings )
255
	{
256
		$defaultString = array_fill_keys( ['id', 's1', 's2', 'p1', 'p2'], '' );
257
		$strings = array_filter( $strings, 'is_array' );
258
		foreach( $strings as &$string ) {
259
			$string['type'] = isset( $string['p1'] ) ? 'plural' : 'single';
260
			$string = wp_parse_args( $string, $defaultString );
261
		}
262
		return array_filter( $strings, function( $string ) {
263
			return !empty( $string['id'] );
264
		});
265
	}
266
}
267