Passed
Push — master ( 642023...49ec8d )
by Paul
03:40
created

Schema::setTermCounts()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 0
dl 0
loc 7
ccs 0
cts 7
cp 0
crap 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules;
4
5
use DateTime;
6
use GeminiLabs\SiteReviews\Application;
7
use GeminiLabs\SiteReviews\Database\CountsManager;
8
use GeminiLabs\SiteReviews\Database\OptionManager;
9
use GeminiLabs\SiteReviews\Database\ReviewManager;
10
use GeminiLabs\SiteReviews\Helper;
11
use GeminiLabs\SiteReviews\Modules\Rating;
12
use GeminiLabs\SiteReviews\Modules\Schema\UnknownType;
13
use WP_Post;
14
15
class Schema
16
{
17
	/**
18
	 * @var array
19
	 */
20
	protected $args;
21
22
	/**
23
	 * @var array
24
	 */
25
	protected $ratingCounts;
26
27
	/**
28
	 * @var array
29
	 */
30
	protected $reviewCounts = [];
31
32
	/**
33
	 * @return array
34
	 */
35
	public function build( array $args = [] )
36
	{
37
		$this->args = $args;
38
		$schema = $this->buildSummary( $args );
39
		$reviews = [];
40
		foreach( glsr( ReviewManager::class )->get( $this->args )->results as $review ) {
41
			// Only include critic reviews that have been directly produced by your site, not reviews from third- party sites or syndicated reviews.
42
			// @see https://developers.google.com/search/docs/data-types/review
43
			if( $review->review_type != 'local' )continue;
44
			$reviews[] = $this->buildReview( $review );
45
		}
46
		if( !empty( $reviews )) {
47
			array_walk( $reviews, function( &$review ) {
48
				unset( $review['@context'] );
49
				unset( $review['itemReviewed'] );
50
			});
51
			$schema['review'] = $reviews;
52
		}
53
		return $schema;
54
	}
55
56
	/**
57
	 * @param null|array $args
58
	 * @return array
59
	 */
60
	public function buildSummary( $args = null )
61
	{
62
		if( is_array( $args )) {
63
			$this->args = $args;
64
		}
65
		$buildSummary = glsr( Helper::class )->buildMethodName( $this->getSchemaOptionValue( 'type' ), 'buildSummaryFor' );
66
		$count = array_sum( $this->getRatingCounts() );
67
		$schema = method_exists( $this, $buildSummary )
68
			? $this->$buildSummary()
69
			: $this->buildSummaryForCustom();
70
		if( !empty( $count )) {
71
			$schema->aggregateRating(
72
				$this->getSchemaType( 'AggregateRating' )
73
					->ratingValue( $this->getRatingValue() )
74
					->reviewCount( $count )
75
					->bestRating( Rating::MAX_RATING )
76
					->worstRating( Rating::MIN_RATING )
77
			);
78
		}
79
		$schema = $schema->toArray();
80
		return apply_filters( 'site-reviews/schema/'.$schema['@type'], $schema, $args );
81
	}
82
83
	/**
84
	 * @return void
85
	 */
86
	public function render()
87
	{
88
		if( is_null( glsr()->schemas ))return;
89
		printf( '<script type="application/ld+json">%s</script>', json_encode(
90
			apply_filters( 'site-reviews/schema/all', glsr()->schemas ),
91
			JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
92
		));
93
	}
94
95
	/**
96
	 * @return void
97
	 */
98
	public function store( array $schema )
99
	{
100
		$schemas = glsr()->schemas;
101
		$schemas[] = $schema;
102
		glsr()->schemas = array_map( 'unserialize', array_unique( array_map( 'serialize', $schemas )));
103
	}
104
105
	/**
106
	 * @param object $review
107
	 * @return array
108
	 */
109
	protected function buildReview( $review )
110
	{
111
		$schema = $this->getSchemaType( 'Review' )
112
			->doIf( !in_array( 'title', $this->args['hide'] ), function( $schema ) use( $review ) {
113
				$schema->name( $review->title );
114
			})
115
			->doIf( !in_array( 'excerpt', $this->args['hide'] ), function( $schema ) use( $review ) {
116
				$schema->reviewBody( $review->content );
117
			})
118
			->datePublished(( new DateTime( $review->date )))
119
			->author( $this->getSchemaType( 'Person' )->name( $review->author ))
120
			->itemReviewed( $this->getSchemaType()->name( $this->getSchemaOptionValue( 'name' )));
121
		if( !empty( $review->rating )) {
122
			$schema->reviewRating(
123
				$this->getSchemaType( 'Rating' )
124
					->ratingValue( $review->rating )
125
					->bestRating( Rating::MAX_RATING )
126
					->worstRating( Rating::MIN_RATING )
127
			);
128
		}
129
		return apply_filters( 'site-reviews/schema/review', $schema->toArray(), $review, $this->args );
130
	}
131
132
	/**
133
	 * @param mixed $schema
134
	 * @return mixed
135
	 */
136
	protected function buildSchemaValues( $schema, array $values = [] )
137
	{
138
		foreach( $values as $value ) {
139
			$option = $this->getSchemaOptionValue( $value );
140
			if( empty( $option ))continue;
141
			$schema->$value( $option );
142
		}
143
		return $schema;
144
	}
145
146
	/**
147
	 * @return mixed
148
	 */
149
	protected function buildSummaryForCustom()
150
	{
151
		return $this->buildSchemaValues( $this->getSchemaType(), [
152
			'description', 'image', 'name', 'url',
153
		]);
154
	}
155
156
	/**
157
	 * @return mixed
158
	 */
159
	protected function buildSummaryForLocalBusiness()
160
	{
161
		return $this->buildSchemaValues( $this->buildSummaryForCustom(), [
162
			'address', 'priceRange', 'telephone',
163
		]);
164
	}
165
166
	/**
167
	 * @return mixed
168
	 */
169
	protected function buildSummaryForProduct()
170
	{
171
		$offers = $this->buildSchemaValues( $this->getSchemaType( 'AggregateOffer' ), [
172
			'highPrice', 'lowPrice', 'priceCurrency',
173
		]);
174
		return $this->buildSummaryForCustom()
175
			->offers( $offers )
176
			->setProperty( '@id', $this->getSchemaOptionValue( 'url' ));
177
	}
178
179
	/**
180
	 * @return array
181
	 */
182
	protected function getRatingCounts()
183
	{
184
		if( !isset( $this->ratingCounts )) {
185
			$this->setPostCounts();
186
			$this->setTermCounts();
187
			if( empty( $this->reviewCounts )) {
188
				$this->reviewCounts[] = glsr( CountsManager::class )->getCounts();
189
			}
190
			$counts = array_column( $this->reviewCounts, 'local' );
191
			$this->ratingCounts = glsr( CountsManager::class )->flatten( $counts, [
192
				'min' => $this->args['rating'],
193
			]);
194
		}
195
		return $this->ratingCounts;
196
	}
197
198
	/**
199
	 * @return int|float
200
	 */
201
	protected function getRatingValue()
202
	{
203
		return glsr( Rating::class )->getAverage( $this->getRatingCounts() );
204
	}
205
206
	/**
207
	 * @param string $option
208
	 * @param string $fallback
209
	 * @return string
210
	 */
211
	protected function getSchemaOption( $option, $fallback )
212
	{
213
		$option = strtolower( $option );
214
		if( $schemaOption = trim( (string)get_post_meta( intval( get_the_ID() ), 'schema_'.$option, true ))) {
215
			return $schemaOption;
216
		}
217
		$setting = glsr( OptionManager::class )->get( 'settings.schema.'.$option );
218
		if( is_array( $setting )) {
219
			return $this->getSchemaOptionDefault( $setting, $fallback );
220
		}
221
		return !empty( $setting )
222
			? $setting
223
			: $fallback;
224
	}
225
226
	/**
227
	 * @param string $fallback
228
	 * @return string
229
	 */
230
	protected function getSchemaOptionDefault( array $setting, $fallback )
231
	{
232
		$setting = wp_parse_args( $setting, [
233
			'custom' => '',
234
			'default' => $fallback,
235
		]);
236
		return $setting['default'] != 'custom'
237
			? $setting['default']
238
			: $setting['custom'];
239
	}
240
241
	/**
242
	 * @param string $option
243
	 * @param string $fallback
244
	 * @return void|string
245
	 */
246
	protected function getSchemaOptionValue( $option, $fallback = 'post' )
247
	{
248
		$value = $this->getSchemaOption( $option, $fallback );
249
		if( $value != $fallback ) {
250
			return $value;
251
		}
252
		if( !is_single() && !is_page() )return;
253
		$method = glsr( Helper::class )->buildMethodName( $option, 'getThing' );
254
		if( method_exists( $this, $method )) {
255
			return $this->$method();
256
		}
257
	}
258
259
	/**
260
	 * @param null|string $type
261
	 * @return mixed
262
	 */
263
	protected function getSchemaType( $type = null )
264
	{
265
		if( !is_string( $type )) {
266
			$type = $this->getSchemaOption( 'type', 'LocalBusiness' );
267
		}
268
		$className = glsr( Helper::class )->buildClassName( $type, 'Modules\Schema' );
269
		return class_exists( $className )
270
			? new $className()
271
			: new UnknownType( $type );
272
	}
273
274
	/**
275
	 * @return string
276
	 */
277
	protected function getThingDescription()
278
	{
279
		$post = get_post();
280
		if( !( $post instanceof WP_Post )) {
281
			return '';
282
		}
283
		$text = strip_shortcodes( wp_strip_all_tags( $post->post_excerpt ));
284
		return wp_trim_words( $text, apply_filters( 'excerpt_length', 55 ));
285
	}
286
287
	/**
288
	 * @return string
289
	 */
290
	protected function getThingImage()
291
	{
292
		return (string)get_the_post_thumbnail_url( null, 'large' );
293
	}
294
295
	/**
296
	 * @return string
297
	 */
298
	protected function getThingName()
299
	{
300
		return get_the_title();
301
	}
302
303
	/**
304
	 * @return string
305
	 */
306
	protected function getThingUrl()
307
	{
308
		return (string)get_the_permalink();
309
	}
310
311
	/**
312
	 * @return void
313
	 */
314
	protected function setPostCounts()
315
	{
316
		$postIds = array_filter( array_map( 'trim', explode( ',', $this->args['assigned_to'] )));
317
		foreach( $postIds as $postId ) {
318
			$this->reviewCounts[] = glsr( CountsManager::class )->getPostCounts( $postId );
319
		}
320
	}
321
322
	/**
323
	 * @return void
324
	 */
325
	protected function setTermCounts()
326
	{
327
		$termIds = array_filter( array_map( 'trim', explode( ',', $this->args['category'] )));
328
		foreach( $termIds as $termId ) {
329
			$term = get_term( $termId, Application::TAXONOMY );
330
			if( !isset( $term->term_id ))continue;
331
			$this->reviewCounts[] = glsr( CountsManager::class )->getTermCounts( $termId );
332
		}
333
	}
334
}
335