$join_alias = 'orderitemmeta1';
$table_to_join_on = "{$wpdb->prefix}wc_order_product_lookup";
if ( empty( $sql_clauses['join'] ) ) {
$sql_clauses['join'][] = "JOIN {$wpdb->prefix}woocommerce_order_items orderitems ON orderitems.order_id = {$table_to_join_on}.order_id";
// If we're matching all filters (AND), we'll need multiple JOINs on postmeta.
if ( 'AND' === $match_operator || 1 === count( $sql_clauses['join'] ) ) {
$join_idx = count( $sql_clauses['join'] );
$join_alias = 'orderitemmeta' . $join_idx;
$sql_clauses['join'][] = "JOIN {$wpdb->prefix}woocommerce_order_itemmeta as {$join_alias} ON {$join_alias}.order_item_id = {$table_to_join_on}.order_item_id";
$in_comparator = '=' === $comparator ? 'in' : 'not in';
// Add subquery for products ordered using attributes not used in variations.
$term_attribute_subquery = "select product_id from {$wpdb->prefix}wc_product_attributes_lookup where is_variation_attribute=0 and term_id = %s";
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
$sql_clauses['where'][] = $wpdb->prepare(
( ( {$join_alias}.meta_key = %s AND {$join_alias}.meta_value {$comparator} %s ) or (
{$wpdb->prefix}wc_order_product_lookup.variation_id = 0 and {$wpdb->prefix}wc_order_product_lookup.product_id {$in_comparator} ({$term_attribute_subquery})
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:enable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
// If we're matching multiple attributes and all filters (AND), make sure
// we're matching attributes on the same product.
$num_attribute_filters = count( $sql_clauses['join'] );
for ( $i = 2; $i < $num_attribute_filters; $i++ ) {
$join_alias = 'orderitemmeta' . $i;
$sql_clauses['join'][] = "AND orderitemmeta1.order_item_id = {$join_alias}.order_item_id";
* Returns logic operator for WHERE subclause based on 'match' query argument.
* @param array $query_args Parameters supplied by the user.
protected function get_match_operator( $query_args ) {
if ( ! isset( $query_args['match'] ) ) {
if ( 'all' === strtolower( $query_args['match'] ) ) {
} elseif ( 'any' === strtolower( $query_args['match'] ) ) {
* Returns filtered comma separated ids, based on query arguments from the user.
* @param array $query_args Parameters supplied by the user.
* @param string $field Query field to filter.
* @param string $separator Field separator.
protected function get_filtered_ids( $query_args, $field, $separator = ',' ) {
$ids = isset( $query_args[ $field ] ) && is_array( $query_args[ $field ] ) ? $query_args[ $field ] : array();
* Filter the IDs before retrieving report data.
* Allows filtering of the objects included or excluded from reports.
* @param array $ids List of object Ids.
* @param array $query_args The original arguments for the request.
* @param string $field The object type.
* @param string $context The data store context.
$ids = apply_filters( 'woocommerce_analytics_' . $field, $ids, $query_args, $field, $this->context );
$placeholders = implode( $separator, array_fill( 0, count( $ids ), '%d' ) );
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$ids_str = $wpdb->prepare( "{$placeholders}", $ids );
* Assign report columns once full table name has been assigned.
protected function assign_report_columns() {}