Schema::retrieve()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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