Automattic\WooCommerce\Internal\ProductFilters

QueryClauses::add_taxonomy_clausespublicWC 1.0

Add query clauses for taxonomy filter (e.g., product_cat, product_tag).

Method of the class: QueryClauses{}

No Hooks.

Returns

Array.

Usage

$QueryClauses = new QueryClauses();
$QueryClauses->add_taxonomy_clauses( $args, $chosen_taxonomies ): array;
$args(array) (required)
Query args.
$chosen_taxonomies(array) (required)

Chosen taxonomies array.

  • {$taxonomy:(array)
    Taxonomy name} {
    @type string[] $terms Chosen terms' slug.

QueryClauses::add_taxonomy_clauses() code WC 10.3.3

public function add_taxonomy_clauses( array $args, array $chosen_taxonomies ): array {
	if ( empty( $chosen_taxonomies ) ) {
		return $args;
	}

	global $wpdb;

	$tax_queries = array();

	$all_terms = get_terms(
		array(
			'taxonomy'   => array_keys( $chosen_taxonomies ),
			'slug'       => array_merge( ...array_values( $chosen_taxonomies ) ),
			'hide_empty' => false,
		)
	);

	if ( is_wp_error( $all_terms ) ) {
		/**
		 * No error logging needed here because:
		 * 1. Taxonomy existence is already validated in the initial get_terms() call above
		 * 2. get_terms() only returns WP_Error for invalid taxonomy or rare DB connection issues
		 * 3. If the taxonomy was invalid, we would have failed earlier and never reached this code
		 * 4. Database errors would likely affect the entire request, not just this call
		 */
		return $args;
	}

	$term_ids_by_taxonomy = array();

	foreach ( $all_terms as $term ) {
		$term_ids_by_taxonomy[ $term->taxonomy ][] = $term->term_id;
	}

	foreach ( $term_ids_by_taxonomy as $taxonomy => $term_ids ) {
		if ( empty( $term_ids ) ) {
			continue;
		}

		if ( is_taxonomy_hierarchical( $taxonomy ) ) {
			$expanded_term_ids = $term_ids;

			foreach ( $term_ids as $term_id ) {
				$cache_key = WC_Cache_Helper::get_cache_prefix( CacheController::CACHE_GROUP ) . 'child_terms_' . $taxonomy . '_' . $term_id;
				$children  = wp_cache_get( $cache_key );

				if ( false === $children ) {
					$children = get_terms(
						array(
							'taxonomy'   => $taxonomy,
							'child_of'   => $term_id,
							'fields'     => 'ids',
							'hide_empty' => false,
						)
					);

					if ( ! is_wp_error( $children ) ) {
						wp_cache_set( $cache_key, $children, '', HOUR_IN_SECONDS );
					} else {
						$children = array();
					}
				}

				$expanded_term_ids = array_merge( $expanded_term_ids, $children );
			}

			$term_ids = array_unique( $expanded_term_ids );
		}

		$term_ids_list = '(' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';

		/*
		 * Use EXISTS subquery for taxonomy filtering for several key benefits:
		 *
		 * 1. Performance: EXISTS stops execution as soon as the first matching row is found,
		 *    making it faster than JOIN approaches that need to process all matches.
		 *
		 * 2. No duplicate rows: Unlike JOINs, EXISTS doesn't create duplicate rows when
		 *    a product has multiple matching terms, eliminating the need for DISTINCT.
		 *
		 * 3. Clean boolean logic: We only care IF a product has the terms, not HOW MANY
		 *    or which specific ones, making EXISTS semantically correct.
		 *
		 * 4. Efficient combination: Multiple taxonomy filters can be combined with AND
		 *    without complex GROUP BY logic or performance degradation.
		 */
		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
		$tax_queries[] = $wpdb->prepare(
			"EXISTS (
				SELECT 1 FROM {$wpdb->term_relationships} tr
				INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
				WHERE tr.object_id = {$wpdb->posts}.ID
				AND tt.taxonomy = %s
				AND tt.term_id IN {$term_ids_list}
			)",
			$taxonomy
		);
	}

	if ( ! empty( $tax_queries ) ) {
		$args['where'] .= ' AND (' . implode( ' AND ', $tax_queries ) . ')';
	} else {
		$args['where'] .= ' AND 1=0';
	}

	return $args;
}