diff --git a/includes/class-wc-data-structurer.php b/includes/class-wc-data-structurer.php new file mode 100644 index 00000000000..87d3bd54b99 --- /dev/null +++ b/includes/class-wc-data-structurer.php @@ -0,0 +1,164 @@ +structured_data... + * + * @param array $json Partially formatted JSON-LD + * @return false If the param $json is not an array + */ + public function set_structured_data( $json ) { + if ( ! is_array( $json ) ) { + return false; + } + $this->structured_data[] = $json; + } + + /** + * Formats and returns the structured data... + * + * @return mixed bool|array If $this->structured_data is set, returns the fully formatted and encoded JSON-LD, otherwise returns false + */ + public function get_structured_data() { + if ( ! $this->structured_data ) { + return false; + } + $context['@context'] = 'http://schema.org/'; + + if ( count( $this->structured_data ) > 1 ) { + if ( is_product() ) { + foreach ( $this->structured_data as $value ) { + if ( isset( $value['@type'] ) ) { + if ( 'Product' === $value['@type'] ) { + $product = $value; + } + elseif ( 'Review' === $value['@type'] ) { + $reviews[] = $value; + } + } + } + $structured_data = isset( $reviews ) ? $product + array( 'review' => $reviews ) : $product; + } + elseif ( is_product_category() ) { + $structured_data = array( '@graph' => $this->structured_data ); + } + } + else { + $structured_data = $this->structured_data[0]; + } + return wp_json_encode( $context + $structured_data ); + } + + /** + * Contructor + */ + public function __construct() { + add_action( 'woocommerce_before_shop_loop_item', array( $this, 'init_product_category_structured_data' ) ); + add_action( 'woocommerce_single_product_summary', array( $this, 'init_product_structured_data' ) ); + add_action( 'woocommerce_review_meta', array( $this, 'init_product_review_structured_data' ) ); + add_action( 'wp_footer', array( $this, 'enqueue_structured_data' ) ); + } + + /** + * If structured data is set, echoes the encoded structured data into the `wp_footer` action hook. + */ + public function enqueue_structured_data() { + if ( $structured_data = $this->get_structured_data() ) { + echo ''; + } + } + + /** + * Generates the product category structured data... + * Hooked into the `woocommerce_before_shop_loop_item` action hook... + */ + public function init_product_category_structured_data() { + if ( ! is_product_category() ) { + return; + } + $this->init_product_structured_data(); + } + /** + * Generates the product structured data... + * Hooked into the `woocommerce_single_product_summary` action hook... + * Applies the `woocommerce_product_structured_data` filter hook for clean structured data customization... + */ + public function init_product_structured_data() { + global $product; + + $json['@type'] = 'Product'; + $json['@id'] = 'product-' . get_the_ID(); + $json['name'] = get_the_title(); + $json['image'] = wp_get_attachment_url( $product->get_image_id() ); + $json['description'] = get_the_excerpt(); + $json['url'] = get_the_permalink(); + $json['sku'] = $product->get_sku(); + $json['brand'] = array( + '@type' => 'Thing', + 'name' => $product->get_attribute( __( 'brand', 'woocommerce' ) ) + ); + if ( $product->get_rating_count() ) { + $json['aggregateRating'] = array( + '@type' => 'AggregateRating', + 'ratingValue' => $product->get_average_rating(), + 'ratingCount' => $product->get_rating_count(), + 'reviewCount' => $product->get_review_count() + ); + } + $json['offers'] = array( + '@type' => 'Offer', + 'priceCurrency' => get_woocommerce_currency(), + 'price' => $product->get_price(), + 'itemCondition' => 'http://schema.org/NewCondition', + 'availability' => 'http://schema.org/' . $stock = ( $product->is_in_stock() ? 'InStock' : 'OutOfStock' ), + 'seller' => array( + '@type' => 'Organization', + 'name' => get_bloginfo( 'name' ) + ) + ); + $this->set_structured_data( apply_filters( 'woocommerce_product_structured_data', $json ) ); + } + + /** + * Generates the product review structured data... + * Hooked into the `woocommerce_review_meta` action hook... + * Applies the `woocommerce_product_review_structured_data` filter hook for clean structured data customization... + */ + public function init_product_review_structured_data() { + global $comment; + + $rating = intval( get_comment_meta( $comment->comment_ID, 'rating', true ) ); + + $json['@type'] = 'Review'; + $json['@id'] = 'li-comment-' . get_comment_ID(); + $json['datePublished'] = get_comment_date( 'c' ); + $json['description'] = get_comment_text(); + $json['reviewRating'] = array( + '@type' => 'rating', + 'ratingValue' => $rating + ); + $json['author'] = array( + '@type' => 'Person', + 'name' => get_comment_author() + ); + $this->set_structured_data( apply_filters( 'woocommerce_product_review_structured_data', $json ) ); + } +} diff --git a/includes/wc-template-functions.php b/includes/wc-template-functions.php index a33509b286c..5c993c3ff5a 100644 --- a/includes/wc-template-functions.php +++ b/includes/wc-template-functions.php @@ -686,36 +686,6 @@ if ( ! function_exists( 'woocommerce_show_product_loop_sale_flash' ) ) { } } -if ( ! function_exists( 'woocommerce_get_product_schema' ) ) { - - /** - * Get a products Schema. - * @return string - */ - function woocommerce_get_product_schema() { - global $product; - - $schema = "Product"; - - // Downloadable product schema handling - if ( $product->is_downloadable() ) { - switch ( $product->download_type ) { - case 'application' : - $schema = "SoftwareApplication"; - break; - case 'music' : - $schema = "MusicAlbum"; - break; - default : - $schema = "Product"; - break; - } - } - - return 'http://schema.org/' . $schema; - } -} - if ( ! function_exists( 'woocommerce_get_product_thumbnail' ) ) { /** @@ -1218,7 +1188,7 @@ if ( ! function_exists( 'woocommerce_review_display_comment_text' ) ) { * Display the review content. */ function woocommerce_review_display_comment_text() { - echo '
'; + echo '
'; comment_text(); echo '
'; } diff --git a/templates/content-single-product.php b/templates/content-single-product.php index 8437218bab5..325067f9aa6 100644 --- a/templates/content-single-product.php +++ b/templates/content-single-product.php @@ -36,7 +36,7 @@ if ( ! defined( 'ABSPATH' ) ) { } ?> -
> +
> - -
diff --git a/templates/single-product/meta.php b/templates/single-product/meta.php index 3cb081a1165..97e47f2f071 100644 --- a/templates/single-product/meta.php +++ b/templates/single-product/meta.php @@ -32,7 +32,7 @@ $tag_count = sizeof( get_the_terms( $post->ID, 'product_tag' ) ); get_sku() || $product->is_type( 'variable' ) ) ) : ?> - get_sku() ) ? $sku : __( 'N/A', 'woocommerce' ); ?> + get_sku() ) ? $sku : __( 'N/A', 'woocommerce' ); ?> diff --git a/templates/single-product/price.php b/templates/single-product/price.php index 9ae39769410..5787e401bdd 100644 --- a/templates/single-product/price.php +++ b/templates/single-product/price.php @@ -23,12 +23,8 @@ if ( ! defined( 'ABSPATH' ) ) { global $product; ?> -
+

get_price_html(); ?>

- - - -
diff --git a/templates/single-product/product-image.php b/templates/single-product/product-image.php index d3550f21aa0..f403c22d0e0 100644 --- a/templates/single-product/product-image.php +++ b/templates/single-product/product-image.php @@ -32,7 +32,7 @@ global $post, $product; 'title' => $props['title'], 'alt' => $props['alt'], ) ); - echo apply_filters( 'woocommerce_single_product_image_html', sprintf( '%s', $props['url'], $props['caption'], $image ), $post->ID ); + echo apply_filters( 'woocommerce_single_product_image_html', sprintf( '%s', $props['url'], $props['caption'], $image ), $post->ID ); } else { echo apply_filters( 'woocommerce_single_product_image_html', sprintf( '%s', wc_placeholder_img_src(), __( 'Placeholder', 'woocommerce' ) ), $post->ID ); } diff --git a/templates/single-product/rating.php b/templates/single-product/rating.php index f82559514c7..b9866c9e259 100644 --- a/templates/single-product/rating.php +++ b/templates/single-product/rating.php @@ -32,14 +32,14 @@ $average = $product->get_average_rating(); if ( $rating_count > 0 ) : ?> -
+
- ', '' ); ?> - ' . $rating_count . '' ); ?> + ', '' ); ?> + ' . $rating_count . '' ); ?>
- (' . $review_count . '' ); ?>) + (' . $review_count . '' ); ?>)
diff --git a/templates/single-product/review-meta.php b/templates/single-product/review-meta.php index f763901fe04..1667d773ddf 100644 --- a/templates/single-product/review-meta.php +++ b/templates/single-product/review-meta.php @@ -30,13 +30,13 @@ if ( '0' === $comment->comment_approved ) { ?>

- (' . esc_attr__( 'verified owner', 'woocommerce' ) . ') '; } - ?>– : + ?>– :

comment_ID, 'rating', true ) ); if ( $rating && get_option( 'woocommerce_enable_review_rating' ) === 'yes' ) { ?> -
- +
+
-
  • id="li-comment-"> +
  • id="li-comment-">
    diff --git a/templates/single-product/short-description.php b/templates/single-product/short-description.php index 3aa40da882a..8adc6b0548c 100644 --- a/templates/single-product/short-description.php +++ b/templates/single-product/short-description.php @@ -27,6 +27,6 @@ if ( ! $post->post_excerpt ) { } ?> -
    +
    post_excerpt ) ?>
    diff --git a/templates/single-product/title.php b/templates/single-product/title.php index d3b2679582e..5e87c0f607c 100644 --- a/templates/single-product/title.php +++ b/templates/single-product/title.php @@ -20,4 +20,4 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } -the_title( '

    ', '

    ' ); +the_title( '

    ', '

    ' ); diff --git a/woocommerce.php b/woocommerce.php index 8fd54d966a3..23cc51e49f0 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -295,6 +295,7 @@ final class WooCommerce { include_once( 'includes/class-wc-customer.php' ); // Customer class include_once( 'includes/class-wc-shortcodes.php' ); // Shortcodes class include_once( 'includes/class-wc-embed.php' ); // Embeds + include_once( 'includes/class-wc-data-structurer.php' ); // Data Structurer class } /** @@ -328,8 +329,9 @@ final class WooCommerce { // Classes/actions loaded for the frontend and for ajax requests. if ( $this->is_request( 'frontend' ) ) { - $this->cart = new WC_Cart(); // Cart class, stores the cart contents - $this->customer = new WC_Customer(); // Customer class, handles data such as customer location + $this->cart = new WC_Cart(); // Cart class, stores the cart contents + $this->customer = new WC_Customer(); // Customer class, handles data such as customer location + $this->data_structurer = new WC_Data_Structurer(); // Microdata class, generates and encodes a JSON-LD } $this->load_webhooks();