Merge pull request #12101 from woocommerce/product-crud-related-products

[Product CRUD] Product crud related products
This commit is contained in:
Mike Jolley 2016-10-18 19:08:59 +01:00 committed by GitHub
commit ad4e99c3e5
4 changed files with 146 additions and 133 deletions

View File

@ -21,6 +21,36 @@ if ( ! defined( 'ABSPATH' ) ) {
*/ */
abstract class WC_Abstract_Legacy_Product extends WC_Data { 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). * The product's type (simple, variable etc).
* *

View File

@ -2076,57 +2076,6 @@ class WC_Product extends WC_Abstract_Legacy_Product {
return absint( $this->shipping_class_id ); 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. * Returns a single product attribute.
* *
@ -2303,84 +2252,6 @@ class WC_Product extends WC_Abstract_Legacy_Product {
return sprintf( '%s &ndash; %s', $identifier, $this->get_title() ); return sprintf( '%s &ndash; %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. * Save taxonomy terms.
* *

View File

@ -700,7 +700,7 @@ function wc_get_product_variation_attributes( $variation_id ) {
* @return array * @return array
*/ */
function wc_get_product_cat_ids( $product_id ) { 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 ) { foreach ( $product_cats as $product_cat ) {
$product_cats = array_merge( $product_cats, get_ancestors( $product_cat, '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' ), '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;
}

View File

@ -13,7 +13,7 @@
* @see https://docs.woocommerce.com/document/template-structure/ * @see https://docs.woocommerce.com/document/template-structure/
* @author WooThemes * @author WooThemes
* @package WooCommerce/Templates * @package WooCommerce/Templates
* @version 1.6.4 * @version 2.7.0
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
@ -26,7 +26,7 @@ if ( empty( $product ) || ! $product->exists() ) {
return; 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; return;
} }
@ -37,7 +37,6 @@ $args = apply_filters( 'woocommerce_related_products_args', array(
'posts_per_page' => $posts_per_page, 'posts_per_page' => $posts_per_page,
'orderby' => $orderby, 'orderby' => $orderby,
'post__in' => $related, 'post__in' => $related,
'post__not_in' => array( $product->id ),
) ); ) );
$products = new WP_Query( $args ); $products = new WP_Query( $args );