JezK
Edit File: Schema.php
<?php namespace GeminiLabs\SiteReviews\Modules; use GeminiLabs\SiteReviews\Database\OptionManager; use GeminiLabs\SiteReviews\Database\RatingManager; use GeminiLabs\SiteReviews\Helper; use GeminiLabs\SiteReviews\Helpers\Arr; use GeminiLabs\SiteReviews\Modules\SchemaParser; use GeminiLabs\SiteReviews\Modules\Schema\BaseType; use GeminiLabs\SiteReviews\Modules\Schema\UnknownType; use GeminiLabs\SiteReviews\Review; class Schema { protected array $args; protected array $keyValues = []; protected array $ratingCounts; /** * @var \GeminiLabs\SiteReviews\Reviews|array */ protected $reviews; /** * @param \GeminiLabs\SiteReviews\Reviews|array $reviews */ public function build(array $args = [], $reviews = []): array { $this->args = $args; $this->reviews = $reviews; $schema = $this->buildSummary($args); if (!empty($schema)) { $reviewSchema = $this->buildReviews(); array_walk($reviewSchema, function (&$review) { unset($review['@context']); unset($review['itemReviewed']); }); } if (!empty($reviewSchema)) { $schema['review'] = $reviewSchema; } return $schema; } public function buildSummary(array $args = [], array $ratings = []): array { if (!empty($args)) { $this->args = $args; } $buildSummary = Helper::buildMethodName('buildSummaryFor', $this->getSchemaOptionValue('type')); $count = array_sum($this->getRatingCounts($ratings)); if (!glsr()->filterBool('schema/is-empty', empty($count))) { $schema = Helper::ifTrue(method_exists($this, $buildSummary), [$this, $buildSummary], [$this, 'buildSummaryForCustom'] ); $schema->aggregateRating( $this->getSchemaType('AggregateRating') ->ratingValue($this->getRatingValue()) ->reviewCount($count) ->bestRating(Rating::max()) ->worstRating(Rating::min()) ); $schema = $schema->toArray(); $type = $schema['@type']; return glsr()->filterArray("schema/{$type}", $schema, $args); } return []; } public function buildSummaryForCustom(): BaseType { return $this->buildSchemaValues($this->getSchemaType(), [ 'description', 'identifier', 'image', 'name', 'url', ]); } public function buildSummaryForLocalBusiness(): BaseType { return $this->buildSchemaValues($this->buildSummaryForCustom(), [ 'address', 'priceRange', 'telephone', ]); } public function buildSummaryForProduct(): BaseType { $offerType = $this->getSchemaOption('offerType', 'AggregateOffer'); $offers = $this->buildSchemaValues($this->getSchemaType($offerType), [ 'highPrice', 'lowPrice', 'price', 'priceCurrency', ]); $schema = $this->buildSummaryForCustom(); if (empty($schema->toArray()['@id'])) { $schema->setProperty('identifier', $this->getSchemaOptionValue('url').'#product'); // this is converted to @id } return $schema->doIf(!empty($offers->getProperties()), function ($schema) use ($offers) { $schema->offers($offers); }); } public function exists(): bool { return !empty($this->retrieve()); } public function generate(): array { $schema = glsr(SchemaParser::class)->generate(); $this->store($schema); return $schema; } public function render(): void { if ($schema = $this->retrieve()) { $json = (string) wp_json_encode($schema, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES); printf('<script type="application/ld+json" class="%s-schema">%s</script>', glsr()->id, $json); } } public function retrieve(): array { $schema = glsr()->retrieve('schemas', []); $schema = Arr::consolidate($schema); return glsr()->filterArray('schema/all', $schema); } public function store(array $schema): void { if (!empty($schema)) { $schemas = $this->retrieve(); $schemas[] = $schema; $schemas = array_map('unserialize', array_unique(array_map('serialize', $schemas))); glsr()->store('schemas', $schemas); } } protected function buildReview(Review $review): array { $schema = $this->getSchemaType('Review') ->doIf(!in_array('title', $this->args['hide']), function ($schema) use ($review) { $schema->name($review->title); }) ->doIf(!in_array('excerpt', $this->args['hide']), function ($schema) use ($review) { $schema->reviewBody($review->content); }) ->datePublished(new \DateTime($review->date)) ->author($this->getSchemaType('Person')->name($review->author())) // ->url($this->getSchemaOptionValue('url')."#review-{$review->ID}") ->itemReviewed($this->getSchemaType()->name($this->getSchemaOptionValue('name'))); if (!empty($review->rating)) { $schema->reviewRating( $this->getSchemaType('Rating') ->ratingValue($review->rating) ->bestRating(Rating::max()) ->worstRating(Rating::min()) ); } return glsr()->filterArray('schema/review', $schema->toArray(), $review, $this->args); } protected function buildReviews(): array { $reviews = []; foreach ($this->reviews as $review) { // Only include critic reviews that have been directly produced by your site, not reviews from third-party sites or syndicated reviews. // @see https://developers.google.com/search/docs/data-types/review if ('local' === $review->type) { $reviews[] = $this->buildReview($review); } } return $reviews; } protected function buildSchemaValues(BaseType $schema, array $values = []): BaseType { foreach ($values as $value) { $option = $this->getSchemaOptionValue($value); if (!empty($option)) { $schema->$value($option); } } return $schema; } protected function getRatingCounts(array $ratings = []): array { if (empty($this->ratingCounts)) { $this->ratingCounts = Helper::ifTrue(!empty($ratings), $ratings, function () { return glsr(RatingManager::class)->ratings($this->args); }); } return $this->ratingCounts; } protected function getRatingValue(): float { return (float) glsr(Rating::class)->average($this->getRatingCounts()); } protected function getSchemaOption(string $option, string $fallback): string { $option = strtolower($option); if ($schemaOption = trim((string) get_post_meta(intval(get_the_ID()), "schema_{$option}", true))) { return $schemaOption; } $setting = glsr(OptionManager::class)->get("settings.schema.{$option}"); if (is_array($setting)) { return $this->getSchemaOptionDefault($setting, $fallback); } return Helper::ifEmpty($setting, $fallback, $strict = true); } protected function getSchemaOptionDefault(array $setting, string $fallback): string { $setting = wp_parse_args($setting, [ 'custom' => '', 'default' => $fallback, ]); return Helper::ifTrue('custom' === $setting['default'], $setting['custom'], $setting['default'] ); } protected function getSchemaOptionValue(string $option, string $fallback = 'post'): string { if (array_key_exists($option, $this->keyValues)) { return (string) $this->keyValues[$option]; } $value = $this->getSchemaOption($option, $fallback); if ($value !== $fallback) { return $this->setAndGetKeyValue($option, $value); } $method = Helper::buildMethodName('getThing', $option); if (!method_exists($this, $method)) { return ''; } return $this->setAndGetKeyValue($option, $this->$method()); } protected function getSchemaType(?string $type = null): BaseType { if (!is_string($type)) { $type = $this->getSchemaOption('type', 'LocalBusiness'); } $className = Helper::buildClassName($type, 'Modules\Schema'); if (class_exists($className)) { return new $className(); } return new UnknownType($type); } protected function getThingDescription(): string { if (is_archive()) { $text = (string) get_the_archive_description(); } elseif (is_singular()) { $post = get_post(); $text = Arr::getAs('string', $post, 'post_excerpt'); if (empty($text)) { $text = Arr::getAs('string', $post, 'post_content'); } } if (!empty($text)) { if (function_exists('excerpt_remove_blocks')) { $text = excerpt_remove_blocks($text); } $text = strip_shortcodes($text); $text = wpautop($text); $text = wptexturize($text); $text = wp_strip_all_tags($text); $text = str_replace(']]>', ']]>', $text); return wp_trim_words($text, apply_filters('excerpt_length', 55)); } return ''; } protected function getThingImage(): string { if (is_singular()) { return (string) get_the_post_thumbnail_url(null, 'large'); } // You will need to use the "site-reviews/schema/<schema_type>" // filter hook to set the image for archive pages. return ''; } protected function getThingName(): string { if (is_archive()) { return wp_strip_all_tags((string) get_the_archive_title()); } if (is_singular()) { return (string) get_the_title(); } return ''; } protected function getThingUrl(): string { $queried = get_queried_object(); if (is_singular()) { $url = get_the_permalink(); } elseif (is_category()) { $url = get_category_link($queried); } elseif (is_tag()) { $url = get_tag_link($queried); } elseif (is_author() && $queried instanceof \WP_User) { $url = get_author_posts_url($queried->ID); } elseif (is_post_type_archive()) { $url = get_post_type_archive_link($queried->name); } elseif (is_tax() && $queried instanceof \WP_Term) { $url = get_term_link($queried); } if (!empty($url) && !is_wp_error($url)) { return (string) $url; } return ''; } protected function setAndGetKeyValue(string $option, string $value): string { $this->keyValues[$option] = $value; return $value; } }