Passed
Push — master ( 75fa9a...bcead5 )
by Paul
04:36
created

SiteReviews   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 376
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 145
dl 0
loc 376
ccs 0
cts 229
cp 0
rs 5.5199
c 0
b 0
f 0
wmc 56

23 Methods

Rating   Name   Duplication   Size   Complexity  
A buildOptionAvatar() 0 8 2
A generateAvatar() 0 12 4
A buildOptionAssignedTo() 0 10 3
A generateSchema() 0 5 2
A buildOptionTitle() 0 7 3
A buildOptionContent() 0 5 2
A buildReviews() 0 8 2
A buildOptionRating() 0 7 2
A buildReview() 0 11 3
A getClass() 0 7 2
A buildOptionAuthor() 0 7 3
A build() 0 16 2
A buildOptionDate() 0 14 4
A buildOptionResponse() 0 9 2
A wrap() 0 4 1
A getExcerpt() 0 7 2
A getOption() 0 6 2
A isOptionEnabled() 0 3 1
A normalizeText() 0 9 2
A isHiddenOrEmpty() 0 3 2
A buildIntlExcerpt() 0 25 5
A isHidden() 0 6 3
A buildExcerpt() 0 15 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;
6
use GeminiLabs\SiteReviews\Database\OptionManager;
7
use GeminiLabs\SiteReviews\Helper;
8
use GeminiLabs\SiteReviews\Modules\Date;
9
use GeminiLabs\SiteReviews\Modules\Html;
10
use GeminiLabs\SiteReviews\Modules\Html\Builder;
11
use GeminiLabs\SiteReviews\Modules\Html\Partial;
12
use GeminiLabs\SiteReviews\Modules\Html\Review;
13
use GeminiLabs\SiteReviews\Modules\Html\Template;
14
use GeminiLabs\SiteReviews\Modules\Rating;
15
use GeminiLabs\SiteReviews\Modules\Schema;
16
use WP_Post;
17
18
class SiteReviews
19
{
20
	/**
21
	 * @var array
22
	 */
23
	protected $args;
24
25
	/**
26
	 * @var int
27
	 */
28
	protected $current;
29
30
	/**
31
	 * @var array
32
	 */
33
	protected $options;
34
35
	/**
36
	 * @var object
37
	 */
38
	protected $reviews;
39
40
	/**
41
	 * @return void|string
42
	 */
43
	public function build( array $args = [] )
44
	{
45
		$this->args = $args;
46
		$this->options = glsr( Helper::class )->flattenArray( glsr( OptionManager::class )->all() );
47
		$this->reviews = glsr( Database::class )->getReviews( $args );
48
		$this->generateSchema();
49
		$navigation = wp_validate_boolean( $this->args['pagination'] )
50
			? glsr( Partial::class )->build( 'pagination', ['total' => $this->reviews->max_num_pages] )
51
			: '';
52
		return glsr( Template::class )->build( 'templates/reviews', [
53
			'context' => [
54
				'class' => $this->getClass(),
55
				'id' => $this->args['id'],
56
				'navigation' => $navigation,
57
			],
58
			'reviews' => $this->buildReviews(),
59
		]);
60
	}
61
62
	/**
63
	 * @return array
64
	 */
65
	public function buildReviews()
66
	{
67
		$reviews = [];
68
		foreach( $this->reviews->results as $index => $review ) {
69
			$this->current = $index;
70
			$reviews[] = $this->buildReview( $review );
71
		}
72
		return $reviews;
73
	}
74
75
	/**
76
	 * @param object $review
77
	 * @return object
78
	 */
79
	protected function buildReview( $review )
80
	{
81
		$review = apply_filters( 'site-reviews/review/build/before', (array)$review );
82
		$reviewValues = [];
83
		foreach( $review as $key => $value ) {
84
			$method = glsr( Helper::class )->buildMethodName( $key, 'buildOption' );
85
			if( !method_exists( $this, $method ))continue;
86
			$reviewValues[$key] = $this->$method( $key, $value );
87
		}
88
		$reviewValues = apply_filters( 'site-reviews/review/build/after', $reviewValues );
89
		return new Review( $reviewValues );
90
	}
91
92
	/**
93
	 * @param string $key
94
	 * @param string $value
95
	 * @return void|string
96
	 */
97
	protected function buildOptionAssignedTo( $key, $value )
98
	{
99
		if( $this->isHiddenOrEmpty( $key, 'settings.reviews.assigned_links.enabled' ))return;
100
		$post = get_post( intval( $value ));
101
		if( !( $post instanceof WP_Post ))return;
102
		$permalink = glsr( Builder::class )->a( get_the_title( $post->ID ), [
103
			'href' => get_the_permalink( $post->ID ),
104
		]);
105
		$assignedTo = sprintf( __( 'Review of %s', 'site-reviews' ), $permalink );
106
		return $this->wrap( $key, '<span>'.$assignedTo.'</span>' );
107
	}
108
109
	/**
110
	 * @param string $key
111
	 * @param string $value
112
	 * @return void|string
113
	 */
114
	protected function buildOptionAuthor( $key, $value )
115
	{
116
		if( $this->isHidden( $key ))return;
117
		$prefix = !$this->isOptionEnabled( 'settings.reviews.avatars.enabled' )
118
			? apply_filters( 'site-reviews/review/author/prefix', '&mdash;' )
119
			: '';
120
		return $this->wrap( $key, $prefix.'<span>'.$value.'</span>' );
121
	}
122
123
	/**
124
	 * @param string $key
125
	 * @param string $value
126
	 * @return void|string
127
	 */
128
	protected function buildOptionAvatar( $key, $value )
129
	{
130
		if( $this->isHidden( $key, 'settings.reviews.avatars.enabled' ))return;
131
		$size = $this->getOption( 'settings.reviews.avatars.size', 40 );
132
		return $this->wrap( $key, glsr( Builder::class )->img([
133
			'src' => $this->generateAvatar( $value ),
134
			'height' => $size,
135
			'width' => $size,
136
		]));
137
	}
138
139
	/**
140
	 * @param string $key
141
	 * @param string $value
142
	 * @return void|string
143
	 */
144
	protected function buildOptionContent( $key, $value )
145
	{
146
		$text = $this->normalizeText( $value );
147
		if( $this->isHiddenOrEmpty( $key, $text ))return;
148
		return $this->wrap( $key, '<p>'.$text.'</p>' );
149
	}
150
151
	/**
152
	 * @param string $key
153
	 * @param string $value
154
	 * @return void|string
155
	 */
156
	protected function buildOptionDate( $key, $value )
157
	{
158
		if( $this->isHidden( $key ))return;
159
		$dateFormat = $this->getOption( 'settings.reviews.date.format', 'default' );
160
		if( $dateFormat == 'relative' ) {
161
			$date = glsr( Date::class )->relative( $value );
162
		}
163
		else {
164
			$format = $dateFormat == 'custom'
165
				? $this->getOption( 'settings.reviews.date.custom', 'M j, Y' )
166
				: (string)get_option( 'date_format' );
167
			$date = date_i18n( $format, strtotime( $value ));
168
		}
169
		return $this->wrap( $key, '<span>'.$date.'</span>' );
170
	}
171
172
	/**
173
	 * @param string $key
174
	 * @param string $value
175
	 * @return void|string
176
	 */
177
	protected function buildOptionRating( $key, $value )
178
	{
179
		if( $this->isHiddenOrEmpty( $key, $value ))return;
180
		$rating = glsr( Html::class )->buildPartial( 'star-rating', [
181
			'rating' => $value,
182
		]);
183
		return $this->wrap( $key, $rating );
184
	}
185
186
	/**
187
	 * @param string $key
188
	 * @param string $value
189
	 * @return void|string
190
	 */
191
	protected function buildOptionResponse( $key, $value )
192
	{
193
		if( $this->isHiddenOrEmpty( $key, $value ))return;
194
		$title = sprintf( __( 'Response from %s', 'site-reviews' ), get_bloginfo( 'name' ));
195
		$text = $this->normalizeText( $value );
196
		$text = '<p><strong>'.$title.'</strong></p><p>'.$text.'</p>';
197
		return $this->wrap( $key,
198
			glsr( Builder::class )->div( $text, ['class' => 'glsr-review-response-inner'] ).
199
			glsr( Builder::class )->div( ['class' => 'glsr-review-response-background'] )
200
		);
201
	}
202
203
	/**
204
	 * @param string $key
205
	 * @param string $value
206
	 * @return void|string
207
	 */
208
	protected function buildOptionTitle( $key, $value )
209
	{
210
		if( $this->isHidden( $key ))return;
211
		if( empty( $value )) {
212
			$value = __( 'No Title', 'site-reviews' );
213
		}
214
		return $this->wrap( $key, '<h3>'.$value.'</h3>' );
215
	}
216
217
	/**
218
	 * @param string $avatarUrl
219
	 * @return string
220
	 */
221
	protected function generateAvatar( $avatarUrl )
222
	{
223
		$review = $this->reviews->results[$this->current];
224
		if( !$this->isOptionEnabled( 'settings.reviews.avatars.regenerate' )
225
			|| $review->review_type != 'local' ) {
226
			return $avatarUrl;
227
		}
228
		$authorIdOrEmail = get_the_author_meta( 'ID', $review->user_id );
229
		if( empty( $authorIdOrEmail )) {
230
			$authorIdOrEmail = $review->email;
231
		}
232
		return (string)get_avatar_url( $authorIdOrEmail );
233
	}
234
235
	/**
236
	 * @return void
237
	 */
238
	protected function generateSchema()
239
	{
240
		if( !wp_validate_boolean( $this->args['schema'] ))return;
241
		glsr( Schema::class )->store(
242
			glsr( Schema::class )->build( $this->args )
243
		);
244
	}
245
246
	/**
247
	 * @return string
248
	 */
249
	protected function getClass()
250
	{
251
		$style = apply_filters( 'site-reviews/reviews/style', 'glsr-style' );
252
		$pagination = $this->args['pagination'] == 'ajax'
253
			? 'glsr-ajax-pagination'
254
			: '';
255
		return trim( 'glsr-reviews '.$style.' '.$pagination.' '.$this->args['class'] );
256
	}
257
258
	/**
259
	 * @param string $text
260
	 * @return string
261
	 */
262
	protected function getExcerpt( $text )
263
	{
264
		$limit = intval( $this->getOption( 'settings.reviews.excerpt.length', 55 ));
265
		$text = extension_loaded( 'intl' )
266
			? $this->buildIntlExcerpt( $text, $limit )
267
			: $this->buildExcerpt( $text, $limit );
268
		return nl2br( $text );
269
	}
270
271
	/**
272
	 * @param string $text
273
	 * @param int $limit
274
	 * @return string
275
	 */
276
	public function buildExcerpt( $text, $limit )
277
	{
278
		if( str_word_count( $text, 0 ) <= $limit ) {
279
			return $text;
280
		}
281
		$words = array_keys( str_word_count( $text, 2 ));
282
		$excerpt = ltrim( substr( $text, 0, $words[$limit] ));
283
284
		$hiddenText = substr( $text, strlen( $excerpt ));
285
		$showMore = glsr( Builder::class )->span( $hiddenText, [
286
			'class' => 'glsr-hidden glsr-hidden-text',
287
			'data-show-less' => __( 'Show less', 'site-reviews' ),
288
			'data-show-more' => __( 'Show more', 'site-reviews' ),
289
		]);
290
		return $excerpt.$showMore;
291
	}
292
293
	/**
294
	 * @param string $text
295
	 * @param int $limit
296
	 * @return string
297
	 */
298
	public function buildIntlExcerpt( $text, $limit )
299
	{
300
		$words = \IntlBreakIterator::createWordInstance( '' );
301
		$words->setText( $text );
302
		$count = 0;
303
		$break = strlen( $text );
304
		foreach( $words as $offset ){
305
			if( \IntlBreakIterator::WORD_NONE === $words->getRuleStatus() )continue;
0 ignored issues
show
Bug introduced by
The method getRuleStatus() does not exist on IntlBreakIterator. It seems like you code against a sub-type of IntlBreakIterator such as IntlRuleBasedBreakIterator. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

305
			if( \IntlBreakIterator::WORD_NONE === $words->/** @scrutinizer ignore-call */ getRuleStatus() )continue;
Loading history...
306
			$count++;
307
			if( $count == $limit ) {
308
				$break = $offset;
309
				break;
310
			}
311
		}
312
		$excerpt = substr( $text, 0, $break );
313
		$hiddenText = substr( $text, $break );
314
		if( empty( $hiddenText )) {
315
			return $excerpt;
316
		}
317
		$showMore = glsr( Builder::class )->span( $hiddenText, [
318
			'class' => 'glsr-hidden glsr-hidden-text',
319
			'data-show-less' => __( 'Show less', 'site-reviews' ),
320
			'data-show-more' => __( 'Show more', 'site-reviews' ),
321
		]);
322
		return $excerpt.$showMore;
323
	}
324
325
	/**
326
	 * @param string $path
327
	 * @param mixed $fallback
328
	 * @return mixed
329
	 */
330
	protected function getOption( $path, $fallback = '' )
331
	{
332
		if( array_key_exists( $path, $this->options )) {
333
			return $this->options[$path];
334
		}
335
		return $fallback;
336
	}
337
338
	/**
339
	 * @param string $key
340
	 * @param string $path
341
	 * @return bool
342
	 */
343
	protected function isHidden( $key, $path = '' )
344
	{
345
		$isOptionEnabled = !empty( $path )
346
			? $this->isOptionEnabled( $path )
347
			: true;
348
		return in_array( $key, $this->args['hide'] ) || !$isOptionEnabled;
349
	}
350
351
	/**
352
	 * @param string $key
353
	 * @param string $value
354
	 * @return bool
355
	 */
356
	protected function isHiddenOrEmpty( $key, $value )
357
	{
358
		return $this->isHidden( $key ) || empty( $value );
359
	}
360
361
	/**
362
	 * @param string $path
363
	 * @return bool
364
	 */
365
	protected function isOptionEnabled( $path )
366
	{
367
		return $this->getOption( $path ) == 'yes';
368
	}
369
370
	/**
371
	 * @param string $text
372
	 * @return string
373
	 */
374
	protected function normalizeText( $text )
375
	{
376
		$text = wp_kses( $text, wp_kses_allowed_html() );
377
		$text = convert_smilies( wptexturize( strip_shortcodes( $text )));
378
		$text = str_replace( ']]>', ']]&gt;', $text );
379
		$text = preg_replace( '/(\R){2,}/', '$1', $text );
380
		return $this->isOptionEnabled( 'settings.reviews.excerpt.enabled' )
381
			? $this->getExcerpt( $text )
382
			: $text;
383
	}
384
385
	/**
386
	 * @param string $key
387
	 * @param string $value
388
	 * @return string
389
	 */
390
	protected function wrap( $key, $value )
391
	{
392
		return glsr( Builder::class )->div( $value, [
393
			'class' => 'glsr-review-'.$key,
394
		]);
395
	}
396
}
397