WC_Structured_Data::generate_product_data()publicWC 1.0

Generates Product structured data.

Hooked into woocommerce_single_product_summary hook.

Method of the class: WC_Structured_Data{}

Return

null. Nothing (null).

Usage

$WC_Structured_Data = new WC_Structured_Data();
$WC_Structured_Data->generate_product_data( $product );
$product(WC_Product)
Product data .
Default: null)

WC_Structured_Data::generate_product_data() code WC 9.7.1

public function generate_product_data( $product = null ) {
	if ( ! is_object( $product ) ) {
		global $product;
	}

	if ( ! is_a( $product, 'WC_Product' ) ) {
		return;
	}

	$shop_name = get_bloginfo( 'name' );
	$shop_url  = home_url();
	$currency  = get_woocommerce_currency();
	$permalink = get_permalink( $product->get_id() );
	$image     = wp_get_attachment_url( $product->get_image_id() );

	$markup = array(
		'@type'       => 'Product',
		'@id'         => $permalink . '#product', // Append '#product' to differentiate between this @id and the @id generated for the Breadcrumblist.
		'name'        => wp_kses_post( $product->get_name() ),
		'url'         => $permalink,
		'description' => wp_strip_all_tags( do_shortcode( $product->get_short_description() ? $product->get_short_description() : $product->get_description() ) ),
	);

	if ( $image ) {
		$markup['image'] = $image;
	}

	// Declare SKU or fallback to ID.
	if ( $product->get_sku() ) {
		$markup['sku'] = $product->get_sku();
	} else {
		$markup['sku'] = $product->get_id();
	}

	// Prepare GTIN and load it if it's valid.
	$gtin = $this->prepare_gtin( $product->get_global_unique_id() );
	if ( $this->is_valid_gtin( $gtin ) ) {
		$markup['gtin'] = $gtin;
	}

	if ( '' !== $product->get_price() ) {
		// Assume prices will be valid until the end of next year, unless on sale and there is an end date.
		$price_valid_until = gmdate( 'Y-12-31', time() + YEAR_IN_SECONDS );

		if ( $product->is_type( ProductType::VARIABLE ) ) {
			$lowest  = $product->get_variation_price( 'min', false );
			$highest = $product->get_variation_price( 'max', false );

			if ( $lowest === $highest ) {
				$markup_offer = array(
					'@type'              => 'Offer',
					'priceSpecification' => array(
						array(
							'@type'                 => 'UnitPriceSpecification',
							'price'                 => wc_format_decimal( $lowest, wc_get_price_decimals() ),
							'priceCurrency'         => $currency,
							'valueAddedTaxIncluded' => wc_prices_include_tax(),
							'validThrough'          => $price_valid_until,
						),
					),
				);
			} else {
				$markup_offer = array(
					'@type'      => 'AggregateOffer',
					'lowPrice'   => wc_format_decimal( $lowest, wc_get_price_decimals() ),
					'highPrice'  => wc_format_decimal( $highest, wc_get_price_decimals() ),
					'offerCount' => count( $product->get_children() ),
				);

				if ( $product->is_on_sale() ) {
					$children                = array_map( 'wc_get_product', $product->get_children() );
					$lowest_child_sale_price = $highest;

					foreach ( $children as $child ) {
						$child_sale_price = $child->get_sale_price();

						if ( empty( $child_sale_price ) || (int) $child_sale_price > (int) $lowest_child_sale_price ) {
							continue;
						}

						$lowest_child_sale_price = $child_sale_price;
						$date_on_sale_to         = $child->get_date_on_sale_to();
						$sale_price_valid_until  = $date_on_sale_to
							? gmdate( 'Y-m-d', $date_on_sale_to->getTimestamp() )
							: null;
					}

					$markup_offer['priceSpecification'] = array(
						array(
							'@type'                 => 'UnitPriceSpecification',
							'priceType'             => 'https://schema.org/SalePrice',
							'price'                 => wc_format_decimal( $lowest_child_sale_price, wc_get_price_decimals() ),
							'priceCurrency'         => $currency,
							'valueAddedTaxIncluded' => wc_prices_include_tax(),
							'validThrough'          => $sale_price_valid_until ?? $price_valid_until,
						),
					);
				}
			}
		} elseif ( $product->is_type( ProductType::GROUPED ) ) {
			$tax_display_mode = get_option( 'woocommerce_tax_display_shop' );
			$children         = array_filter( array_map( 'wc_get_product', $product->get_children() ), 'wc_products_array_filter_visible_grouped' );
			$price_function   = 'incl' === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax';

			foreach ( $children as $child ) {
				if ( '' !== $child->get_regular_price() ) {
					$child_prices[] = $price_function( $child, array( 'price' => $child->get_regular_price() ) );
				}
				if ( '' !== $child->get_sale_price() ) {
					$child_sale_prices[] = $price_function( $child, array( 'price' => $child->get_sale_price() ) );
				}
			}
			if ( empty( $child_prices ) ) {
				$min_price = 0;
			} else {
				$min_price = min( $child_prices );
			}
			if ( empty( $child_sale_prices ) ) {
				$min_sale_price = 0;
			} else {
				$min_sale_price = min( $child_sale_prices );
			}

			$unit_price_specification = array(
				'@type'                 => 'UnitPriceSpecification',
				'price'                 => wc_format_decimal( $min_price, wc_get_price_decimals() ),
				'priceCurrency'         => $currency,
				'valueAddedTaxIncluded' => wc_prices_include_tax(),
				'validThrough'          => $price_valid_until,
			);
			if ( $product->is_on_sale() && $min_price !== $min_sale_price ) {
				// `priceType` should only be specified in prices which are not the current offer.
				// https://developers.google.com/search/docs/appearance/structured-data/merchant-listing#sale-pricing-example
				$unit_price_specification['priceType'] = 'https://schema.org/ListPrice';
			}
			$markup_offer = array(
				'@type'              => 'Offer',
				'priceSpecification' => array(
					$unit_price_specification,
				),
			);

			if ( $product->is_on_sale() && $min_price !== $min_sale_price ) {
				if ( $product->get_date_on_sale_to() ) {
					$sale_price_valid_until = gmdate( 'Y-m-d', $product->get_date_on_sale_to()->getTimestamp() );
				}

				$markup_offer['priceSpecification'][] = array(
					'@type'                 => 'UnitPriceSpecification',
					'price'                 => wc_format_decimal( $min_sale_price, wc_get_price_decimals() ),
					'priceCurrency'         => $currency,
					'valueAddedTaxIncluded' => wc_prices_include_tax(),
					'validThrough'          => $sale_price_valid_until ?? $price_valid_until,
				);
			}
		} else {
			$unit_price_specification = array(
				'@type'                 => 'UnitPriceSpecification',
				'price'                 => wc_format_decimal( $product->get_regular_price(), wc_get_price_decimals() ),
				'priceCurrency'         => $currency,
				'valueAddedTaxIncluded' => wc_prices_include_tax(),
				'validThrough'          => $price_valid_until,
			);
			if ( $product->is_on_sale() ) {
				// `priceType` should only be specified in prices which are not the current offer.
				// https://developers.google.com/search/docs/appearance/structured-data/merchant-listing#sale-pricing-example
				$unit_price_specification['priceType'] = 'https://schema.org/ListPrice';
			}
			$markup_offer = array(
				'@type'              => 'Offer',
				'priceSpecification' => array(
					$unit_price_specification,
				),
			);

			if ( $product->is_on_sale() ) {
				if ( $product->get_date_on_sale_to() ) {
					$sale_price_valid_until = gmdate( 'Y-m-d', $product->get_date_on_sale_to()->getTimestamp() );
				}

				$markup_offer['priceSpecification'][] = array(
					'@type'                 => 'UnitPriceSpecification',
					'price'                 => wc_format_decimal( $product->get_sale_price(), wc_get_price_decimals() ),
					'priceCurrency'         => $currency,
					'valueAddedTaxIncluded' => wc_prices_include_tax(),
					'validThrough'          => $sale_price_valid_until ?? $price_valid_until,
				);
			}
		}

		if ( $product->is_in_stock() ) {
			$stock_status_schema = ( 'onbackorder' === $product->get_stock_status() ) ? 'BackOrder' : 'InStock';
		} else {
			$stock_status_schema = 'OutOfStock';
		}

		$markup_offer += array(
			'priceValidUntil' => $sale_price_valid_until ?? $price_valid_until,
			'availability'    => 'http://schema.org/' . $stock_status_schema,
			'url'             => $permalink,
			'seller'          => array(
				'@type' => 'Organization',
				'name'  => $shop_name,
				'url'   => $shop_url,
			),
		);
		if (
			( ! empty( $markup_offer['price'] ) ||
				! empty( $markup_offer['lowPrice'] ) ||
				! empty( $markup_offer['highPrice'] )
			) && empty( $markup_offer['priceCurrency'] )
		) {
			$markup_offer['priceCurrency'] = $currency;
		}

		$markup['offers'] = array( apply_filters( 'woocommerce_structured_data_product_offer', $markup_offer, $product ) );
	}

	if ( $product->get_rating_count() && wc_review_ratings_enabled() ) {
		$markup['aggregateRating'] = array(
			'@type'       => 'AggregateRating',
			'ratingValue' => $product->get_average_rating(),
			'reviewCount' => $product->get_review_count(),
		);

		// Markup 5 most recent rating/review.
		$comments = get_comments(
			array(
				'number'      => 5,
				'post_id'     => $product->get_id(),
				'status'      => 'approve',
				'post_status' => 'publish',
				'post_type'   => 'product',
				'parent'      => 0,
				'meta_query'  => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
					array(
						'key'     => 'rating',
						'type'    => 'NUMERIC',
						'compare' => '>',
						'value'   => 0,
					),
				),
			)
		);

		if ( $comments ) {
			$markup['review'] = array();
			foreach ( $comments as $comment ) {
				$markup['review'][] = array(
					'@type'         => 'Review',
					'reviewRating'  => array(
						'@type'       => 'Rating',
						'bestRating'  => '5',
						'ratingValue' => get_comment_meta( $comment->comment_ID, 'rating', true ),
						'worstRating' => '1',
					),
					'author'        => array(
						'@type' => 'Person',
						'name'  => get_comment_author( $comment ),
					),
					'reviewBody'    => get_comment_text( $comment ),
					'datePublished' => get_comment_date( 'c', $comment ),
				);
			}
		}
	}

	// Check we have required data.
	if ( empty( $markup['aggregateRating'] ) && empty( $markup['offers'] ) && empty( $markup['review'] ) ) {
		return;
	}

	$this->set_data( apply_filters( 'woocommerce_structured_data_product', $markup, $product ) );
}