diff --git a/includes/abstracts/abstract-wc-legacy-product.php b/includes/abstracts/abstract-wc-legacy-product.php index 965346d7b08..731d1265a6c 100644 --- a/includes/abstracts/abstract-wc-legacy-product.php +++ b/includes/abstracts/abstract-wc-legacy-product.php @@ -21,6 +21,36 @@ if ( ! defined( 'ABSPATH' ) ) { */ abstract class WC_Abstract_Legacy_Product extends WC_Data { + /** + * Get and return related products. + * @deprecated 2.7.0 Use wc_get_related_products instead. + */ + public function get_related( $limit = 5 ) { + _deprecated_function( 'WC_Product::get_related', '2.7', 'wc_get_related_products' ); + return wc_get_related_products( $this->get_id(), $limit ); + } + + /** + * Retrieves related product terms. + * @deprecated 2.7.0 Use wc_get_product_term_ids instead. + */ + protected function get_related_terms( $term ) { + _deprecated_function( 'WC_Product::get_related_terms', '2.7', 'wc_get_product_term_ids' ); + return array_merge( array( 0 ), wc_get_product_term_ids( $this->get_id(), $term ) ); + } + + /** + * Builds the related posts query. + * @deprecated 2.7.0 Use wc_get_related_products_query instead. + */ + protected function build_related_query( $cats_array, $tags_array, $exclude_ids, $limit ) { + _deprecated_function( 'WC_Product::build_related_query', '2.7', 'wc_get_related_products_query' ); + return wc_get_related_products_query( $cats_array, $tags_array, $exclude_ids, $limit ); + } + + + + /** * The product's type (simple, variable etc). * diff --git a/includes/abstracts/abstract-wc-product.php b/includes/abstracts/abstract-wc-product.php index 97ce0e021a0..d2be0d2e460 100644 --- a/includes/abstracts/abstract-wc-product.php +++ b/includes/abstracts/abstract-wc-product.php @@ -2076,57 +2076,6 @@ class WC_Product extends WC_Abstract_Legacy_Product { return absint( $this->shipping_class_id ); } - /** - * Get and return related products. - * - * Notes: - * - Results are cached in a transient for faster queries. - * - To make results appear random, we query and extra 10 products and shuffle them. - * - To ensure we always have enough results, it will check $limit before returning the cached result, if not recalc. - * - This used to rely on transient version to invalidate cache, but to avoid multiple transients we now just expire daily. - * This means if a related product is edited and no longer related, it won't be removed for 24 hours. Acceptable trade-off for performance. - * - Saving a product will flush caches for that product. - * - * @param int $limit (default: 5) Should be an integer greater than 0. - * @return array Array of post IDs - */ - public function get_related( $limit = 5 ) { - global $wpdb; - - $transient_name = 'wc_related_' . $this->get_id(); - $related_posts = get_transient( $transient_name ); - $limit = $limit > 0 ? $limit : 5; - - // We want to query related posts if they are not cached, or we don't have enough - if ( false === $related_posts || sizeof( $related_posts ) < $limit ) { - // Related products are found from category and tag - $tags_array = $this->get_related_terms( 'product_tag' ); - $cats_array = $this->get_related_terms( 'product_cat' ); - - // Don't bother if none are set - if ( 1 === sizeof( $cats_array ) && 1 === sizeof( $tags_array ) ) { - $related_posts = array(); - } else { - // Sanitize - $exclude_ids = array_map( 'absint', array_merge( array( 0, $this->get_id() ), $this->get_upsells() ) ); - - // Generate query - but query an extra 10 results to give the appearance of random results - $query = $this->build_related_query( $cats_array, $tags_array, $exclude_ids, $limit + 10 ); - - // Get the posts - $related_posts = $wpdb->get_col( implode( ' ', $query ) ); - } - - set_transient( $transient_name, $related_posts, DAY_IN_SECONDS ); - } - - // Randomise the results - shuffle( $related_posts ); - - // Limit the returned results - return array_slice( $related_posts, 0, $limit ); - } - /** * Returns a single product attribute. * @@ -2303,84 +2252,6 @@ class WC_Product extends WC_Abstract_Legacy_Product { return sprintf( '%s – %s', $identifier, $this->get_title() ); } - /** - * Retrieves related product terms. - * - * @param string $term - * @return array - */ - protected function get_related_terms( $term ) { - $terms_array = array( 0 ); - - $terms = apply_filters( 'woocommerce_get_related_' . $term . '_terms', wp_get_post_terms( $this->get_id(), $term ), $this->get_id() ); - foreach ( $terms as $term ) { - $terms_array[] = $term->term_id; - } - - return array_map( 'absint', $terms_array ); - } - - /** - * Builds the related posts query. - * - * @param array $cats_array - * @param array $tags_array - * @param array $exclude_ids - * @param int $limit - * @return string - */ - protected function build_related_query( $cats_array, $tags_array, $exclude_ids, $limit ) { - global $wpdb; - - $limit = absint( $limit ); - - $query = array(); - $query['fields'] = "SELECT DISTINCT ID FROM {$wpdb->posts} p"; - $query['join'] = " INNER JOIN {$wpdb->postmeta} pm ON ( pm.post_id = p.ID AND pm.meta_key='_visibility' )"; - $query['join'] .= " INNER JOIN {$wpdb->term_relationships} tr ON (p.ID = tr.object_id)"; - $query['join'] .= " INNER JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)"; - $query['join'] .= " INNER JOIN {$wpdb->terms} t ON (t.term_id = tt.term_id)"; - - if ( get_option( 'woocommerce_hide_out_of_stock_items' ) === 'yes' ) { - $query['join'] .= " INNER JOIN {$wpdb->postmeta} pm2 ON ( pm2.post_id = p.ID AND pm2.meta_key='_stock_status' )"; - } - - $query['where'] = " WHERE 1=1"; - $query['where'] .= " AND p.post_status = 'publish'"; - $query['where'] .= " AND p.post_type = 'product'"; - $query['where'] .= " AND p.ID NOT IN ( " . implode( ',', $exclude_ids ) . " )"; - $query['where'] .= " AND pm.meta_value IN ( 'visible', 'catalog' )"; - - if ( get_option( 'woocommerce_hide_out_of_stock_items' ) === 'yes' ) { - $query['where'] .= " AND pm2.meta_value = 'instock'"; - } - - $relate_by_category = apply_filters( 'woocommerce_product_related_posts_relate_by_category', true, $this->get_id() ); - $relate_by_tag = apply_filters( 'woocommerce_product_related_posts_relate_by_tag', true, $this->get_id() ); - - if ( $relate_by_category || $relate_by_tag ) { - $query['where'] .= ' AND ('; - - if ( $relate_by_category ) { - $query['where'] .= " ( tt.taxonomy = 'product_cat' AND t.term_id IN ( " . implode( ',', $cats_array ) . " ) ) "; - if ( $relate_by_tag ) { - $query['where'] .= ' OR '; - } - } - - if ( $relate_by_tag ) { - $query['where'] .= " ( tt.taxonomy = 'product_tag' AND t.term_id IN ( " . implode( ',', $tags_array ) . " ) ) "; - } - - $query['where'] .= ')'; - } - - $query['limits'] = " LIMIT {$limit} "; - $query = apply_filters( 'woocommerce_product_related_posts_query', $query, $this->get_id() ); - - return $query; - } - /** * Save taxonomy terms. * diff --git a/includes/wc-product-functions.php b/includes/wc-product-functions.php index 0c89e29bb48..8d4f7cde9dc 100644 --- a/includes/wc-product-functions.php +++ b/includes/wc-product-functions.php @@ -700,7 +700,7 @@ function wc_get_product_variation_attributes( $variation_id ) { * @return array */ function wc_get_product_cat_ids( $product_id ) { - $product_cats = wp_get_post_terms( $product_id, 'product_cat', array( "fields" => "ids" ) ); + $product_cats = wc_get_product_term_ids( $product_id, 'product_cat' ); foreach ( $product_cats as $product_cat ) { $product_cats = array_merge( $product_cats, get_ancestors( $product_cat, 'product_cat' ) ); @@ -751,3 +751,116 @@ function wc_get_product_visibility_options() { 'hidden' => __( 'Hidden', 'woocommerce' ), ) ); } + +/** + * Get related products based on product category and tags. + * + * @since 2.7.0 + * @param int $product_id Product ID. + * @param int $limit Limit of results. + * @param array $exclude_ids Exclude IDs from the results. + * @return array + */ +function wc_get_related_products( $product_id, $limit = 5, $exclude_ids = array() ) { + global $wpdb; + + $product_id = absint( $product_id ); + $exclude_ids = array_merge( array( 0, $product_id ), $exclude_ids ); + $transient_name = 'wc_related_' . $product_id; + $related_posts = get_transient( $transient_name ); + $limit = $limit > 0 ? $limit : 5; + + // We want to query related posts if they are not cached, or we don't have enough. + if ( false === $related_posts || count( $related_posts ) < $limit ) { + $cats_array = apply_filters( 'woocommerce_product_related_posts_relate_by_category', true, $product_id ) ? apply_filters( 'woocommerce_get_related_product_cat_terms', wc_get_product_term_ids( $product_id, 'product_cat' ), $product_id ) : array(); + $tags_array = apply_filters( 'woocommerce_product_related_posts_relate_by_tag', true, $product_id ) ? apply_filters( 'woocommerce_get_related_product_tag_terms', wc_get_product_term_ids( $product_id, 'product_tag' ), $product_id ) : array(); + + // Don't bother if none are set, unless woocommerce_product_related_posts_force_display is set to true in which case all products are related. + if ( empty( $cats_array ) && empty( $tags_array ) && ! apply_filters( 'woocommerce_product_related_posts_force_display', false, $product_id ) ) { + $related_posts = array(); + } else { + // Generate query - but query an extra 10 results to give the appearance of random results when later shuffled. + $related_posts = $wpdb->get_col( implode( ' ', apply_filters( 'woocommerce_product_related_posts_query', wc_get_related_products_query( $cats_array, $tags_array, $exclude_ids, $limit + 10 ), $product_id ) ) ); + } + + set_transient( $transient_name, $related_posts, DAY_IN_SECONDS ); + } + + shuffle( $related_posts ); + + return array_slice( $related_posts, 0, $limit ); +} + +/** + * Retrieves product term ids for a taxonomy. + * + * @since 2.7.0 + * @param int $product_id Product ID. + * @param string $taxonomy Taxonomy slug. + * @return array + */ +function wc_get_product_term_ids( $product_id, $taxonomy ) { + return wp_list_pluck( get_the_terms( $product_id, $taxonomy ), 'term_id' ); +} + +/** + * Builds the related posts query. + * + * @since 2.7.0 + * @param array $cats_array List of categories IDs. + * @param array $tags_array List of tags IDs. + * @param array $exclude_ids Excluded IDs. + * @param int $limit Limit of results. + * @return string + */ +function wc_get_related_products_query( $cats_array, $tags_array, $exclude_ids, $limit ) { + global $wpdb; + + // Arrays to string. + $exclude_ids = implode( ',', array_map( 'absint', $exclude_ids ) ); + $cats_array = implode( ',', array_map( 'absint', $cats_array ) ); + $tags_array = implode( ',', array_map( 'absint', $tags_array ) ); + + $limit = absint( $limit ); + $query = array(); + $query['fields'] = "SELECT DISTINCT ID FROM {$wpdb->posts} p"; + $query['join'] = " INNER JOIN {$wpdb->postmeta} pm ON ( pm.post_id = p.ID AND pm.meta_key='_visibility' )"; + $query['join'] .= " INNER JOIN {$wpdb->term_relationships} tr ON (p.ID = tr.object_id)"; + $query['join'] .= " INNER JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)"; + $query['join'] .= " INNER JOIN {$wpdb->terms} t ON (t.term_id = tt.term_id)"; + + if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) { + $query['join'] .= " INNER JOIN {$wpdb->postmeta} pm2 ON ( pm2.post_id = p.ID AND pm2.meta_key='_stock_status' )"; + } + + $query['where'] = ' WHERE 1=1'; + $query['where'] .= " AND p.post_status = 'publish'"; + $query['where'] .= " AND p.post_type = 'product'"; + $query['where'] .= " AND p.ID NOT IN ( {$exclude_ids} )"; + $query['where'] .= " AND pm.meta_value IN ( 'visible', 'catalog' )"; + + if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) { + $query['where'] .= " AND pm2.meta_value = 'instock'"; + } + + if ( $cats_array || $tags_array ) { + $query['where'] .= ' AND ('; + + if ( $cats_array ) { + $query['where'] .= " ( tt.taxonomy = 'product_cat' AND t.term_id IN ( {$cats_array} ) ) "; + if ( $relate_by_tag ) { + $query['where'] .= ' OR '; + } + } + + if ( $tags_array ) { + $query['where'] .= " ( tt.taxonomy = 'product_tag' AND t.term_id IN ( {$tags_array} ) ) "; + } + + $query['where'] .= ')'; + } + + $query['limits'] = " LIMIT {$limit} "; + + return $query; +} diff --git a/templates/single-product/related.php b/templates/single-product/related.php index 330f6356030..d3570b888e4 100644 --- a/templates/single-product/related.php +++ b/templates/single-product/related.php @@ -13,7 +13,7 @@ * @see https://docs.woocommerce.com/document/template-structure/ * @author WooThemes * @package WooCommerce/Templates - * @version 1.6.4 + * @version 2.7.0 */ if ( ! defined( 'ABSPATH' ) ) { @@ -26,7 +26,7 @@ if ( empty( $product ) || ! $product->exists() ) { return; } -if ( ! $related = $product->get_related( $posts_per_page ) ) { +if ( ! $related = wc_get_related_products( $product->get_id(), $posts_per_page, $product->get_upsell_ids() ) ) { return; } @@ -37,7 +37,6 @@ $args = apply_filters( 'woocommerce_related_products_args', array( 'posts_per_page' => $posts_per_page, 'orderby' => $orderby, 'post__in' => $related, - 'post__not_in' => array( $product->id ), ) ); $products = new WP_Query( $args );