wc_customer_bought_product() │ WC 1.0
Checks if a user (by email or ID or both) has bought an item.
Hooks from the function
Returns
true|false.
Usage
wc_customer_bought_product( $customer_email, $user_id, $product_id );
- $customer_email(string) (required)
- Customer email to check.
- $user_id(int) (required)
- User ID to check.
- $product_id(int) (required)
- Product ID to check.
wc_customer_bought_product() wc customer bought product code WC 10.8.1
function wc_customer_bought_product( $customer_email, $user_id, $product_id ) {
global $wpdb;
$result = apply_filters( 'woocommerce_pre_customer_bought_product', null, $customer_email, $user_id, $product_id );
if ( null !== $result ) {
return $result;
}
/**
* Whether to use lookup tables - it can optimize performance, but correctness depends on the frequency of the AS job.
*
* @since 9.7.0
*
* @param bool $enabled
* @param string $customer_email Customer email to check.
* @param int $user_id User ID to check.
* @param int $product_id Product ID to check.
* @return bool
*/
$use_lookup_tables = apply_filters( 'woocommerce_customer_bought_product_use_lookup_tables', false, $customer_email, $user_id, $product_id );
if ( $use_lookup_tables ) {
// Lookup tables get refreshed along with the `woocommerce_reports` transient version (due to async processing).
// With high orders placement rate, this caching here will be short-lived (suboptimal for BFCM/Christmas and busy stores in general).
$cache_version = WC_Cache_Helper::get_transient_version( 'woocommerce_reports' );
} elseif ( '' === $customer_email && $user_id ) {
// Optimized: for specific customers version with orders count (it's a user meta from in-memory populated datasets).
// Best-case scenario for caching here, as it only depends on the customer orders placement rate.
$cache_version = wc_get_customer_order_count( $user_id );
} else {
// Fallback: create, update, and delete operations on orders clears caches and refreshes `orders` transient version.
// With high orders placement rate, this caching here will be short-lived (suboptimal for BFCM/Christmas and busy stores in general).
// For the core, no use-cases for this branch. Themes/extensions are still valid use-cases.
$cache_version = WC_Cache_Helper::get_transient_version( 'orders' );
}
$aggregation_version = 'v2'; // Update the version when modifying the aggregation implementation to ensure the cache is repopulated.
$cache_group = 'orders';
$cache_key = 'wc_customer_bought_product_' . md5( $customer_email . '-' . $user_id . '-' . $use_lookup_tables . '-' . $aggregation_version );
$cache_value = wp_cache_get( $cache_key, $cache_group );
if ( isset( $cache_value['value'], $cache_value['version'] ) && $cache_value['version'] === $cache_version ) {
$result = $cache_value['value'];
} else {
// Identify the customer using the provided data. Use Customer ID to optimize performance in the following SQL
// queries. If an account has been deleted, use the supplied ID to ensure graceful handling.
$user = null;
$original_user_id = $user_id;
if ( ! $user_id && $customer_email && is_email( $customer_email ) ) {
$user = get_user_by( 'email', $customer_email );
$user_id = $user->ID ?? $user_id;
}
if ( $user_id && ! $user ) {
$user = get_user_by( 'id', $user_id );
$user_id = $user->ID ?? $user_id;
}
// Deduplicate emails to ensure the Customer ID remains the primary performance driver in subsequent SQL queries.
$emails = array( $customer_email );
if ( $original_user_id ) {
$user_email = $user->user_email ?? '';
if ( $user_email && is_email( $user_email ) && strtolower( $user_email ) === strtolower( $customer_email ) ) {
$emails = array();
}
}
$emails = array_unique( array_filter( $emails, static fn( $email ) => $email && is_email( $email ) ) );
if ( empty( $emails ) && ! $user_id ) {
wp_cache_set(
$cache_key,
array(
'version' => $cache_version,
'value' => array(),
),
$cache_group,
MONTH_IN_SECONDS
);
return false;
}
$emails = array_map( 'esc_sql', $emails );
$statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
$statuses = array_map( static fn( $status ) => "wc-$status", $statuses );
$order_table = OrdersTableDataStore::get_orders_table_name();
$identity_clause = array();
if ( $user_id ) {
$identity_clause[] = 'orders.customer_id = ' . absint( $user_id );
}
if ( ! empty( $emails ) ) {
$identity_clause[] = "orders.billing_email IN ( '" . implode( "','", $emails ) . "' )";
}
$identity_clause = implode( ' OR ', $identity_clause );
if ( $use_lookup_tables ) {
// HPOS: yes, Lookup table: yes.
$sql = "SELECT DISTINCT product_or_variation_id
FROM (
SELECT CASE WHEN product_id != 0 THEN product_id ELSE variation_id END AS product_or_variation_id
FROM {$wpdb->prefix}wc_order_product_lookup lookup
INNER JOIN $order_table AS orders ON lookup.order_id = orders.ID
WHERE orders.status IN ( '" . implode( "','", $statuses ) . "' )
AND ( $identity_clause )
) AS subquery
WHERE product_or_variation_id != 0";
} else {
// HPOS: yes, Lookup table: no.
$sql = "SELECT DISTINCT itemmeta.meta_value
FROM $order_table AS orders
INNER JOIN {$wpdb->prefix}woocommerce_order_items AS items ON orders.id = items.order_id
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS itemmeta ON items.order_item_id = itemmeta.order_item_id
WHERE orders.status IN ( '" . implode( "','", $statuses ) . "' )
AND itemmeta.meta_key IN ( '_product_id', '_variation_id' )
AND itemmeta.meta_value != '0'
AND ( $identity_clause )";
}
} else {
$identity_clause = array();
if ( $user_id ) {
$identity_clause[] = "( postmeta.meta_key = '_customer_user' AND postmeta.meta_value = '" . absint( $user_id ) . "' )";
}
if ( ! empty( $emails ) ) {
$identity_clause[] = "( postmeta.meta_key = '_billing_email' AND postmeta.meta_value IN ( '" . implode( "','", $emails ) . "' ) )";
}
$identity_clause = implode( ' OR ', $identity_clause );
if ( $use_lookup_tables ) {
// HPOS: no, Lookup table: yes.
$sql = "SELECT DISTINCT product_or_variation_id
FROM (
SELECT CASE WHEN lookup.product_id != 0 THEN lookup.product_id ELSE lookup.variation_id END AS product_or_variation_id
FROM {$wpdb->prefix}wc_order_product_lookup AS lookup
INNER JOIN {$wpdb->posts} AS posts ON posts.ID = lookup.order_id
INNER JOIN {$wpdb->postmeta} AS postmeta ON posts.ID = postmeta.post_id
WHERE posts.post_status IN ( 'wc-" . implode( "','wc-", $statuses ) . "' )
AND ( $identity_clause )
) AS subquery
WHERE product_or_variation_id != 0";
} else {
// HPOS: no, Lookup table: no.
$sql = "SELECT DISTINCT itemmeta.meta_value
FROM {$wpdb->prefix}woocommerce_order_items AS items
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS itemmeta ON items.order_item_id = itemmeta.order_item_id
WHERE items.order_id IN (
SELECT posts.ID as order_id
FROM {$wpdb->posts} AS posts
INNER JOIN {$wpdb->postmeta} AS postmeta ON posts.ID = postmeta.post_id
WHERE posts.post_type = 'shop_order'
AND posts.post_status IN ( 'wc-" . implode( "','wc-", $statuses ) . "' )
AND ( $identity_clause )
)
AND itemmeta.meta_key IN ( '_product_id', '_variation_id' )
AND itemmeta.meta_value != '0'";
}
}
$result = array_map( 'absint', $wpdb->get_col( $sql ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
wp_cache_set(
$cache_key,
array(
'version' => $cache_version,
'value' => $result,
),
$cache_group,
MONTH_IN_SECONDS
);
}
return in_array( absint( $product_id ), $result, true );
}