$fields Cleaned field values. * @param WP_Post $post The post. * @param Node_Context $c Page information. * @param Graph $graph The graph, for nodes that are shared (author, images). * @return array|null */ abstract public function build( string $type, array $fields, WP_Post $post, Node_Context $c, Graph $graph ): ?array; /** * A text field, or the fallback when it is empty. * * @since 1.0.0 * * @param array $fields Field values. * @param string $key Field key. * @param string $fallback Fallback. * @return string */ protected function text( array $fields, string $key, string $fallback = '' ): string { $value = isset( $fields[ $key ] ) && is_scalar( $fields[ $key ] ) ? trim( (string) $fields[ $key ] ) : ''; return '' !== $value ? $value : $fallback; } /** * The title of a post as plain text. * * @since 1.0.0 * * @param WP_Post $post Post. * @return string */ protected function title( WP_Post $post ): string { return trim( html_entity_decode( wp_strip_all_tags( get_the_title( $post ) ), ENT_QUOTES, 'UTF-8' ) ); } /** * The description to use when a field is empty: the page's meta description, then the excerpt. * * @since 1.0.0 * * @param WP_Post $post Post. * @param Node_Context $c Page information. * @return string */ protected function description( WP_Post $post, Node_Context $c ): string { $description = $c->description(); if ( '' !== $description ) { return $description; } return trim( wp_strip_all_tags( get_the_excerpt( $post ) ) ); } /** * The address of an image field, or of the featured image when the field is empty. * * @since 1.0.0 * * @param array $fields Field values. * @param string $key Field key. * @param WP_Post $post Post. * @return string */ protected function image_url( array $fields, string $key, WP_Post $post ): string { $id = isset( $fields[ $key ] ) ? (int) $fields[ $key ] : 0; if ( $id < 1 ) { $id = (int) get_post_thumbnail_id( $post ); } return Image_Node::url( $id ); } /** * Adds the post's author to the graph and returns a reference to it. * * @since 1.0.0 * * @param WP_Post $post Post. * @param Graph $graph Graph. * @return array|null */ protected function author( WP_Post $post, Graph $graph ): ?array { $node = Author_Node::build( (int) $post->post_author ); if ( null === $node ) { return null; } $graph->add( $node ); return array( '@id' => $node['@id'] ); } /** * An AggregateRating, only when a rating and the number of ratings were both entered. * * Nothing is made up: without both values there is no rating. * * @since 1.0.0 * * @param array $fields Field values (`rating_value`, `rating_count`). * @return array|null */ protected function aggregate_rating( array $fields ): ?array { $value = $this->text( $fields, 'rating_value' ); $count = (int) $this->text( $fields, 'rating_count' ); if ( '' === $value || $count < 1 || (float) $value < 1 || (float) $value > 5 ) { return null; } return array( '@type' => 'AggregateRating', 'ratingValue' => $this->number( $value ), 'ratingCount' => $count, 'bestRating' => 5, 'worstRating' => 1, ); } /** * A number from its text: a whole number stays whole, anything with a dot is a decimal. * * @since 1.0.0 * * @param string $value Text that has already been checked to be a number. * @return int|float */ protected function number( string $value ) { return false !== strpos( $value, '.' ) ? (float) $value : (int) $value; } /** * The address of an availability value. * * @since 1.0.0 * * @param string $value Value such as `InStock`. * @return string Empty when there is none. */ protected function availability( string $value ): string { return '' !== $value ? 'https://schema.org/' . $value : ''; } /** * An Offer, only with a price and a currency. * * @since 1.0.0 * * @param array $fields Field values. * @param string $prefix Prefix of the field keys: empty for `price`, `offer_` for `offer_price`. * @param string $url Address the offer belongs to. * @return array|null */ protected function offer( array $fields, string $prefix, string $url ): ?array { $price = $this->text( $fields, $prefix . 'price' ); $currency = $this->text( $fields, $prefix . 'currency' ); if ( '' === $price || '' === $currency ) { return null; } return array( '@type' => 'Offer', 'price' => $price, 'priceCurrency' => $currency, 'availability' => $this->availability( $this->text( $fields, $prefix . 'availability' ) ), 'priceValidUntil' => $this->text( $fields, 'price_valid_until' ), 'validFrom' => $this->text( $fields, $prefix . 'valid_from' ), 'url' => $this->text( $fields, $prefix . 'url', $url ), ); } }