wc_customer_bought_product()WC 1.0

Checks if a user (by email or ID or both) has bought an item.

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() 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 );
}