Passed
Push — master ( af979f...1dfed5 )
by Paul
08:16 queued 04:12
created

Schema::build()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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