Passed
Push — master ( c6a124...535cb5 )
by Paul
09:05 queued 01:21
created

Schema   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 348
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 0 Features 1
Metric Value
wmc 43
eloc 133
c 4
b 0
f 1
dl 0
loc 348
ccs 0
cts 223
cp 0
rs 8.96

21 Methods

Rating   Name   Duplication   Size   Complexity  
A buildSummaryForLocalBusiness() 0 4 1
A buildSummaryForCustom() 0 4 1
A build() 0 16 3
A buildSummary() 0 22 3
A setAndGetKeyValue() 0 4 1
A getSchemaOptionValue() 0 15 5
A buildSummaryForProduct() 0 12 2
A buildReview() 0 21 2
A render() 0 8 2
A getRatingValue() 0 3 1
A getThingUrl() 0 3 1
A getThingName() 0 3 1
A buildReviews() 0 11 3
A getSchemaType() 0 12 2
A buildSchemaValues() 0 9 3
A getThingDescription() 0 16 3
A getSchemaOptionDefault() 0 9 1
A store() 0 7 2
A getRatingCounts() 0 8 2
A getSchemaOption() 0 11 3
A getThingImage() 0 3 1

How to fix   Complexity   

Complex Class

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

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