prefix . 'aieo_core_product_vitals'; static $has_vcols = null; if ( $has_vcols === null ) { $has_vcols = ( (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = 'rating_value'", $vitals ) ) ) > 0; } $vrow = $has_vcols ? $wpdb->get_row( $wpdb->prepare( "SELECT rating_value, rating_count FROM `{$vitals}` WHERE product_id = %d AND rating_value IS NOT NULL LIMIT 1", $product_id ), ARRAY_A ) : null; if ( is_array( $vrow ) ) { $count = (int) $vrow['rating_count']; $avg = (float) $vrow['rating_value']; } else { $stats = AIEO_Ratings::instance()->get_stats( $product_id, 'product' ); if ( is_array( $stats ) ) { $count = isset( $stats['count'] ) ? (int) $stats['count'] : 0; $avg = isset( $stats['avg'] ) ? (float) $stats['avg'] : 0.0; } } $min_count = (int) apply_filters( 'aieo_ratings_schema_min_count', self::DEFAULT_MIN_COUNT, $product_id ); if ( $count < $min_count || $avg <= 0.0 ) { return null; } return array( '@type' => 'AggregateRating', 'ratingValue' => number_format( $avg, 1, '.', '' ), 'ratingCount' => $count, 'bestRating' => '5', 'worstRating' => '1', ); } public static function build_reviews( $product_id, $agg = null ) { $product_id = (int) $product_id; if ( $product_id <= 0 ) { return array(); } $reviews = array(); if ( 'yes' === get_option( 'woocommerce_enable_review_rating', 'yes' ) && function_exists( 'get_comments' ) ) { $comments = get_comments( array( 'post_id' => $product_id, 'status' => 'approve', 'number' => 5, 'meta_key' => 'rating', 'meta_compare' => 'EXISTS', 'orderby' => 'comment_date_gmt', 'order' => 'DESC', ) ); foreach ( (array) $comments as $c ) { $r = (int) get_comment_meta( $c->comment_ID, 'rating', true ); if ( $r < 1 || $r > 5 ) { continue; } $body = trim( wp_strip_all_tags( (string) $c->comment_content ) ); if ( '' === $body ) { continue; } $reviews[] = array( '@type' => 'Review', 'reviewRating' => array( '@type' => 'Rating', 'ratingValue' => (string) $r, 'bestRating' => '5', 'worstRating' => '1' ), 'author' => array( '@type' => 'Person', 'name' => ( '' !== $c->comment_author ? $c->comment_author : __( 'Customer', 'ai-eshop-optimizer' ) ) ), 'reviewBody' => self::truncate_review( $body, 500 ), 'datePublished' => mysql2date( 'Y-m-d', $c->comment_date_gmt ), ); } } $seo = class_exists( 'AIEO_DMM_Settings' ) ? (array) AIEO_DMM_Settings::get( 'seo', array() ) : array(); if ( ! empty( $seo['rating_review_enabled'] ) ) { if ( $agg === null ) { $agg = self::build_aggregate_rating( $product_id ); } if ( is_array( $agg ) && ! empty( $agg['ratingValue'] ) ) { $star = max( 1, min( 5, (int) round( (float) $agg['ratingValue'] ) ) ); $means = ( isset( $seo['rating_star_meanings'] ) && is_array( $seo['rating_star_meanings'] ) ) ? $seo['rating_star_meanings'] : array(); $defaults = array( 1 => __( 'Our customers rate this poorly.', 'ai-eshop-optimizer' ), 2 => __( 'Our customers find this below average.', 'ai-eshop-optimizer' ), 3 => __( 'Our customers find this average.', 'ai-eshop-optimizer' ), 4 => __( 'Our customers find this very good.', 'ai-eshop-optimizer' ), 5 => __( 'Our customers find this excellent.', 'ai-eshop-optimizer' ), ); $body = ( isset( $means[ $star ] ) && '' !== trim( (string) $means[ $star ] ) ) ? (string) $means[ $star ] : $defaults[ $star ]; $org = (string) get_bloginfo( 'name' ); $reviews[] = array( '@type' => 'Review', 'reviewRating' => array( '@type' => 'Rating', 'ratingValue' => $agg['ratingValue'], 'bestRating' => '5', 'worstRating' => '1' ), 'author' => array( '@type' => 'Organization', 'name' => ( '' !== $org ? $org : __( 'Verified customers', 'ai-eshop-optimizer' ) ) ), 'reviewBody' => self::truncate_review( $body, 300 ), ); } } return $reviews; } private static function truncate_review( $text, $limit ) { $text = trim( (string) $text ); $len = function_exists( 'mb_strlen' ) ? mb_strlen( $text ) : strlen( $text ); if ( $len <= $limit ) { return $text; } $cut = function_exists( 'mb_substr' ) ? mb_substr( $text, 0, $limit - 1 ) : substr( $text, 0, $limit - 1 ); return rtrim( $cut ) . '…'; } public static function inject_aioseo( $graphs ) { if ( ! is_array( $graphs ) ) { return $graphs; } $agg = self::build_aggregate_rating(); if ( ! $agg ) { return $graphs; } $product_types = array( 'Product', 'ProductGroup' ); foreach ( $graphs as $i => $piece ) { if ( is_array( $piece ) ) { $type = isset( $piece['@type'] ) ? $piece['@type'] : ''; $is_product = is_array( $type ) ? (bool) array_intersect( $product_types, $type ) : in_array( $type, $product_types, true ); if ( ! $is_product ) { continue; } if ( ! empty( $piece['aggregateRating']['@id'] ) ) { $agg['@id'] = $piece['aggregateRating']['@id']; } $graphs[ $i ]['aggregateRating'] = $agg; } elseif ( is_object( $piece ) ) { $type = isset( $piece->{'@type'} ) ? $piece->{'@type'} : ''; $is_product = is_array( $type ) ? (bool) array_intersect( $product_types, $type ) : in_array( $type, $product_types, true ); if ( ! $is_product ) { continue; } if ( ! empty( $piece->aggregateRating->{'@id'} ) ) { $agg['@id'] = $piece->aggregateRating->{'@id'}; } $graphs[ $i ]->aggregateRating = (object) $agg; } } return $graphs; } public static function inject_yoast( $data ) { if ( ! is_array( $data ) ) { return $data; } $agg = self::build_aggregate_rating(); if ( ! $agg ) { return $data; } $data['aggregateRating'] = $agg; return $data; } public static function inject_rankmath( $entity ) { if ( ! is_array( $entity ) ) { return $entity; } $agg = self::build_aggregate_rating(); if ( ! $agg ) { return $entity; } $entity['aggregateRating'] = $agg; return $entity; } private static function has_seo_plugin() { return defined( 'AIOSEO_VERSION' ) || class_exists( 'AIOSEO\\Plugin\\Common\\Main' ) || defined( 'WPSEO_VERSION' ) || class_exists( 'WPSEO_Options' ) || defined( 'RANK_MATH_VERSION' ) || class_exists( 'RankMath' ); } public static function maybe_emit_fallback_schema() { if ( ! function_exists( 'is_product' ) || ! is_product() ) { return; } if ( self::has_seo_plugin() ) { return; } if ( class_exists( 'AIEO_SEO_Store' ) && AIEO_SEO_Store::is_active_provider() && class_exists( 'AIEO_SEO_Head' ) && null !== AIEO_SEO_Head::get( 'product', (int) get_queried_object_id() ) ) { return; } $agg = self::build_aggregate_rating(); if ( ! $agg ) { return; } global $product; if ( ! function_exists( 'wc_get_product' ) ) { return; } $wc_product = is_a( $product, 'WC_Product' ) ? $product : wc_get_product( get_the_ID() ); if ( ! $wc_product ) { return; } $schema = array( '@context' => 'https://schema.org/', '@type' => 'Product', 'name' => (string) $wc_product->get_name(), 'url' => (string) get_permalink( $wc_product->get_id() ), 'aggregateRating' => $agg, ); $sku = (string) $wc_product->get_sku(); if ( $sku !== '' ) { $schema['sku'] = $sku; } echo "\n\n"; } public static function invalidate_head_cache( $product_ids, $object_type = 'product' ) { global $wpdb; if ( 'product' !== (string) $object_type ) { return; } $ids = array_values( array_unique( array_filter( array_map( 'intval', (array) $product_ids ) ) ) ); if ( ! $ids ) { return; } $t = $wpdb->prefix . 'aieo_seo_head_cache'; if ( (string) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $t ) ) !== $t ) { return; } foreach ( array_chunk( $ids, 200 ) as $chunk ) { $ph = implode( ',', array_fill( 0, count( $chunk ), '%d' ) ); $wpdb->query( $wpdb->prepare( "DELETE FROM `{$t}` WHERE object_type = 'product' AND object_id IN ({$ph})", $chunk ) ); } } } AIEO_Ratings_Schema::init();