Passed
Push — master ( 648c0b...8eca98 )
by Paul
09:10
created

SiteReviews   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 371
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 143
dl 0
loc 371
ccs 0
cts 227
cp 0
rs 4.5599
c 0
b 0
f 0
wmc 58

23 Methods

Rating   Name   Duplication   Size   Complexity  
A buildReviews() 0 10 2
A build() 0 16 2
A buildOptionAvatar() 0 9 2
A getExcerptSplit() 0 7 2
A wrap() 0 8 2
A generateAvatar() 0 14 5
A getExcerpt() 0 16 3
A buildOptionAssignedTo() 0 10 3
A getExcerptIntlSplit() 0 12 4
A generateSchema() 0 5 2
A getOption() 0 6 2
A buildOptionTitle() 0 7 3
A isOptionEnabled() 0 3 1
A buildOptionContent() 0 5 2
A normalizeText() 0 10 2
A isHiddenOrEmpty() 0 3 2
A buildOptionRating() 0 5 2
A buildReview() 0 15 4
A getClass() 0 6 2
A isHidden() 0 6 3
A buildOptionAuthor() 0 4 2
A buildOptionDate() 0 14 4
A buildOptionResponse() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like SiteReviews 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 SiteReviews, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules\Html\Partials;
4
5
use GeminiLabs\SiteReviews\Database\OptionManager;
6
use GeminiLabs\SiteReviews\Database\ReviewManager;
7
use GeminiLabs\SiteReviews\Helper;
8
use GeminiLabs\SiteReviews\Modules\Date;
9
use GeminiLabs\SiteReviews\Modules\Html\Builder;
10
use GeminiLabs\SiteReviews\Modules\Html\Partial;
11
use GeminiLabs\SiteReviews\Modules\Html\ReviewHtml;
12
use GeminiLabs\SiteReviews\Modules\Html\Template;
13
use GeminiLabs\SiteReviews\Modules\Polylang;
14
use GeminiLabs\SiteReviews\Modules\Schema;
15
use GeminiLabs\SiteReviews\Review;
16
use IntlRuleBasedBreakIterator;
17
use WP_Post;
18
19
class SiteReviews
20
{
21
	/**
22
	 * @var array
23
	 */
24
	protected $args;
25
26
	/**
27
	 * @var int
28
	 */
29
	protected $current;
30
31
	/**
32
	 * @var array
33
	 */
34
	protected $options;
35
36
	/**
37
	 * @var object
38
	 */
39
	protected $reviews;
40
41
	/**
42
	 * @return void|string
43
	 */
44
	public function build( array $args = [] )
45
	{
46
		$this->args = $args;
47
		$this->options = glsr( Helper::class )->flattenArray( glsr( OptionManager::class )->all() );
48
		$this->reviews = glsr( ReviewManager::class )->get( $args );
49
		$this->generateSchema();
50
		$navigation = wp_validate_boolean( $this->args['pagination'] )
51
			? glsr( Partial::class )->build( 'pagination', ['total' => $this->reviews->max_num_pages] )
52
			: '';
53
		return glsr( Template::class )->build( 'templates/reviews', [
54
			'context' => [
55
				'assigned_to' => $this->args['assigned_to'],
56
				'class' => $this->getClass(),
57
				'id' => $this->args['id'],
58
				'navigation' => $navigation,
59
				'reviews' => $this->buildReviews(),
60
			],
61
		]);
62
	}
63
64
	/**
65
	 * @return string
66
	 */
67
	public function buildReviews()
68
	{
69
		$reviews = '';
70
		foreach( $this->reviews->results as $index => $result ) {
71
			$this->current = $index;
72
			$reviews.= glsr( Template::class )->build( 'templates/review', [
73
				'context' => $this->buildReview( $result )->values,
74
			]);
75
		}
76
		return $reviews;
77
	}
78
79
	/**
80
	 * @param string $key
81
	 * @param string $path
82
	 * @return bool
83
	 */
84
	public function isHidden( $key, $path = '' )
85
	{
86
		$isOptionEnabled = !empty( $path )
87
			? $this->isOptionEnabled( $path )
88
			: true;
89
		return in_array( $key, $this->args['hide'] ) || !$isOptionEnabled;
90
	}
91
92
	/**
93
	 * @param Review $review
94
	 * @return ReviewHtml
95
	 */
96
	protected function buildReview( Review $review )
97
	{
98
		$review = apply_filters( 'site-reviews/review/build/before', $review ); // @todo make this an arrayable object!
99
		$renderedFields = [];
100
		foreach( (array)$review as $key => $value ) {
101
			$method = glsr( Helper::class )->buildMethodName( $key, 'buildOption' );
102
			$field = method_exists( $this, $method )
103
				? $this->$method( $key, $value )
104
				: apply_filters( 'site-reviews/review/build/'.$key, false, $value, $this, $review );
105
			if( $field === false )continue;
106
			$renderedFields[$key] = $field;
107
		}
108
		$this->wrap( $renderedFields, $review );
109
		$renderedFields = apply_filters( 'site-reviews/review/build/after', $renderedFields, $review );
110
		return new ReviewHtml( (array)$renderedFields );
111
	}
112
113
	/**
114
	 * @param string $key
115
	 * @param string $value
116
	 * @return void|string
117
	 */
118
	protected function buildOptionAssignedTo( $key, $value )
119
	{
120
		if( $this->isHidden( $key, 'settings.reviews.assigned_links' ))return;
121
		$post = glsr( Polylang::class )->getPost( $value );
122
		if( !( $post instanceof WP_Post ))return;
123
		$permalink = glsr( Builder::class )->a( get_the_title( $post->ID ), [
124
			'href' => get_the_permalink( $post->ID ),
125
		]);
126
		$assignedTo = sprintf( __( 'Review of %s', 'site-reviews' ), $permalink );
127
		return '<span>'.$assignedTo.'</span>';
128
	}
129
130
	/**
131
	 * @param string $key
132
	 * @param string $value
133
	 * @return void|string
134
	 */
135
	protected function buildOptionAuthor( $key, $value )
136
	{
137
		if( $this->isHidden( $key ))return;
138
		return '<span>'.$value.'</span>';
139
	}
140
141
	/**
142
	 * @param string $key
143
	 * @param string $value
144
	 * @return void|string
145
	 */
146
	protected function buildOptionAvatar( $key, $value )
147
	{
148
		if( $this->isHidden( $key, 'settings.reviews.avatars' ))return;
149
		$size = $this->getOption( 'settings.reviews.avatars_size', 40 );
150
		return glsr( Builder::class )->img([
151
			'height' => $size,
152
			'src' => $this->generateAvatar( $value ),
153
			'style' => sprintf( 'width:%1$spx; height:%1$spx;', $size ),
154
			'width' => $size,
155
		]);
156
	}
157
158
	/**
159
	 * @param string $key
160
	 * @param string $value
161
	 * @return void|string
162
	 */
163
	protected function buildOptionContent( $key, $value )
164
	{
165
		$text = $this->normalizeText( $value );
166
		if( $this->isHiddenOrEmpty( $key, $text ))return;
167
		return '<p>'.$text.'</p>';
168
	}
169
170
	/**
171
	 * @param string $key
172
	 * @param string $value
173
	 * @return void|string
174
	 */
175
	protected function buildOptionDate( $key, $value )
176
	{
177
		if( $this->isHidden( $key ))return;
178
		$dateFormat = $this->getOption( 'settings.reviews.date.format', 'default' );
179
		if( $dateFormat == 'relative' ) {
180
			$date = glsr( Date::class )->relative( $value );
181
		}
182
		else {
183
			$format = $dateFormat == 'custom'
184
				? $this->getOption( 'settings.reviews.date.custom', 'M j, Y' )
185
				: (string)get_option( 'date_format' );
186
			$date = date_i18n( $format, strtotime( $value ));
187
		}
188
		return '<span>'.$date.'</span>';
189
	}
190
191
	/**
192
	 * @param string $key
193
	 * @param string $value
194
	 * @return void|string
195
	 */
196
	protected function buildOptionRating( $key, $value )
197
	{
198
		if( $this->isHiddenOrEmpty( $key, $value ))return;
199
		return glsr( Partial::class )->build( 'star-rating', [
200
			'rating' => $value,
201
		]);
202
	}
203
204
	/**
205
	 * @param string $key
206
	 * @param string $value
207
	 * @return void|string
208
	 */
209
	protected function buildOptionResponse( $key, $value )
210
	{
211
		if( $this->isHiddenOrEmpty( $key, $value ))return;
212
		$title = sprintf( __( 'Response from %s', 'site-reviews' ), get_bloginfo( 'name' ));
213
		$text = $this->normalizeText( $value );
214
		$text = '<p><strong>'.$title.'</strong></p><p>'.$text.'</p>';
215
		$response = glsr( Builder::class )->div( $text, ['class' => 'glsr-review-response-inner'] );
216
		$background = glsr( Builder::class )->div( ['class' => 'glsr-review-response-background'] );
217
		return $response.$background;
218
	}
219
220
	/**
221
	 * @param string $key
222
	 * @param string $value
223
	 * @return void|string
224
	 */
225
	protected function buildOptionTitle( $key, $value )
226
	{
227
		if( $this->isHidden( $key ))return;
228
		if( empty( $value )) {
229
			$value = __( 'No Title', 'site-reviews' );
230
		}
231
		return '<h3>'.$value.'</h3>';
232
	}
233
234
	/**
235
	 * @param string $avatarUrl
236
	 * @return string
237
	 */
238
	protected function generateAvatar( $avatarUrl )
239
	{
240
		$review = $this->reviews->results[$this->current];
241
		if( !$this->isOptionEnabled( 'settings.reviews.avatars_regenerate' ) || $review->review_type != 'local' ) {
242
			return $avatarUrl;
243
		}
244
		$authorIdOrEmail = get_the_author_meta( 'ID', $review->user_id );
245
		if( empty( $authorIdOrEmail )) {
246
			$authorIdOrEmail = $review->email;
247
		}
248
		if( $newAvatar = get_avatar_url( $authorIdOrEmail )) {
249
			return $newAvatar;
250
		}
251
		return $avatarUrl;
252
	}
253
254
	/**
255
	 * @return void
256
	 */
257
	protected function generateSchema()
258
	{
259
		if( !wp_validate_boolean( $this->args['schema'] ))return;
260
		glsr( Schema::class )->store(
261
			glsr( Schema::class )->build( $this->args )
262
		);
263
	}
264
265
	/**
266
	 * @return string
267
	 */
268
	protected function getClass()
269
	{
270
		$pagination = $this->args['pagination'] == 'ajax'
271
			? 'glsr-ajax-pagination'
272
			: '';
273
		return trim( 'glsr-reviews glsr-default '.$pagination.' '.$this->args['class'] );
274
	}
275
276
	/**
277
	 * @param string $text
278
	 * @return string
279
	 */
280
	protected function getExcerpt( $text )
281
	{
282
		$limit = intval( $this->getOption( 'settings.reviews.excerpts_length', 55 ));
283
		$split = extension_loaded( 'intl' )
284
			? $this->getExcerptIntlSplit( $text, $limit )
285
			: $this->getExcerptSplit( $text, $limit );
286
		$hiddenText = substr( $text, $split );
287
		if( !empty( $hiddenText )) {
288
			$showMore = glsr( Builder::class )->span( $hiddenText, [
289
				'class' => 'glsr-hidden glsr-hidden-text',
290
				'data-show-less' => __( 'Show less', 'site-reviews' ),
291
				'data-show-more' => __( 'Show more', 'site-reviews' ),
292
			]);
293
			$text = ltrim( substr( $text, 0, $split )).$showMore;
294
		}
295
		return nl2br( $text );
296
	}
297
298
	/**
299
	 * @param string $text
300
	 * @param int $limit
301
	 * @return int
302
	 */
303
	protected function getExcerptIntlSplit( $text, $limit )
304
	{
305
		$words = IntlRuleBasedBreakIterator::createWordInstance( '' );
306
		$words->setText( $text );
307
		$count = 0;
308
		foreach( $words as $offset ){
309
			if( $words->getRuleStatus() === IntlRuleBasedBreakIterator::WORD_NONE )continue;
310
			$count++;
311
			if( $count != $limit )continue;
312
			return $offset;
313
		}
314
		return strlen( $text );
315
	}
316
317
	/**
318
	 * @param string $text
319
	 * @param int $limit
320
	 * @return int
321
	 */
322
	protected function getExcerptSplit( $text, $limit )
323
	{
324
		if( str_word_count( $text, 0 ) > $limit ) {
325
			$words = array_keys( str_word_count( $text, 2 ));
326
			return $words[$limit];
327
		}
328
		return strlen( $text );
329
	}
330
331
	/**
332
	 * @param string $path
333
	 * @param mixed $fallback
334
	 * @return mixed
335
	 */
336
	protected function getOption( $path, $fallback = '' )
337
	{
338
		if( array_key_exists( $path, $this->options )) {
339
			return $this->options[$path];
340
		}
341
		return $fallback;
342
	}
343
344
	/**
345
	 * @param string $key
346
	 * @param string $value
347
	 * @return bool
348
	 */
349
	protected function isHiddenOrEmpty( $key, $value )
350
	{
351
		return $this->isHidden( $key ) || empty( $value );
352
	}
353
354
	/**
355
	 * @param string $path
356
	 * @return bool
357
	 */
358
	protected function isOptionEnabled( $path )
359
	{
360
		return $this->getOption( $path ) == 'yes';
361
	}
362
363
	/**
364
	 * @param string $text
365
	 * @return string
366
	 */
367
	protected function normalizeText( $text )
368
	{
369
		$text = wp_kses( $text, wp_kses_allowed_html() );
370
		$text = convert_smilies( strip_shortcodes( $text ));
371
		$text = str_replace( ']]>', ']]&gt;', $text );
372
		$text = preg_replace( '/(\R){2,}/', '$1', $text );
373
		if( $this->isOptionEnabled( 'settings.reviews.excerpts' )) {
374
			$text = $this->getExcerpt( $text );
375
		}
376
		return wptexturize( $text );
377
	}
378
379
	/**
380
	 * @return void
381
	 */
382
	protected function wrap( array &$renderedFields, Review $review )
383
	{
384
		$renderedFields = apply_filters( 'site-reviews/review/wrap', $renderedFields, $review );
385
		array_walk( $renderedFields, function( &$value, $key ) use( $review ) {
386
			$value = apply_filters( 'site-reviews/review/wrap/'.$key, $value, $review );
387
			if( empty( $value ))return;
388
			$value = glsr( Builder::class )->div( $value, [
389
				'class' => 'glsr-review-'.$key,
390
			]);
391
		});
392
	}
393
}
394