+/**
+ * Given a taxonomy query, generates SQL to be appended to a main query.
+ *
+ * @since 3.1.0
+ *
+ * @see WP_Tax_Query
+ *
+ * @param array $tax_query A compact tax query
+ * @param string $primary_table
+ * @param string $primary_id_column
+ * @return array
+ */
+function get_tax_sql( $tax_query, $primary_table, $primary_id_column ) {
+ $tax_query_obj = new WP_Tax_Query( $tax_query );
+ return $tax_query_obj->get_sql( $primary_table, $primary_id_column );
+}
+
+/**
+ * Container class for a multiple taxonomy query.
+ *
+ * @since 3.1.0
+ */
+class WP_Tax_Query {
+
+ /**
+ * List of taxonomy queries. A single taxonomy query is an associative array:
+ * - 'taxonomy' string The taxonomy being queried
+ * - 'terms' string|array The list of terms
+ * - 'field' string (optional) Which term field is being used.
+ * Possible values: 'term_id', 'slug' or 'name'
+ * Default: 'term_id'
+ * - 'operator' string (optional)
+ * Possible values: 'IN' and 'NOT IN'.
+ * Default: 'IN'
+ * - 'include_children' bool (optional) Whether to include child terms.
+ * Default: true
+ *
+ * @since 3.1.0
+ * @access public
+ * @var array
+ */
+ var $queries = array();
+
+ /**
+ * The relation between the queries. Can be one of 'AND' or 'OR'.
+ *
+ * @since 3.1.0
+ * @access public
+ * @var string
+ */
+ var $relation;
+
+ /**
+ * PHP4 type constructor.
+ *
+ * Parses a compact tax query and sets defaults.
+ *
+ * @since 3.1.0
+ * @access public
+ *
+ * @param array $tax_query A compact tax query:
+ * array(
+ * 'relation' => 'OR',
+ * array(
+ * 'taxonomy' => 'tax1',
+ * 'terms' => array( 'term1', 'term2' ),
+ * 'field' => 'slug',
+ * ),
+ * array(
+ * 'taxonomy' => 'tax2',
+ * 'terms' => array( 'term-a', 'term-b' ),
+ * 'field' => 'slug',
+ * ),
+ * )
+ *
+ * @return WP_Tax_Query
+ */
+ function WP_Tax_Query( $tax_query ) {
+ if ( isset( $tax_query['relation'] ) && strtoupper( $tax_query['relation'] ) == 'OR' ) {
+ $this->relation = 'OR';
+ } else {
+ $this->relation = 'AND';
+ }
+
+ $defaults = array(
+ 'taxonomy' => '',
+ 'terms' => array(),
+ 'include_children' => true,
+ 'field' => 'term_id',
+ 'operator' => 'IN',
+ );
+
+ foreach ( $tax_query as $query ) {
+ if ( ! is_array( $query ) )
+ continue;
+
+ $query = array_merge( $defaults, $query );
+
+ $query['terms'] = (array) $query['terms'];
+
+ $this->queries[] = $query;
+ }
+ }
+
+ /**
+ * Generates SQL clauses to be appended to a main query.
+ *
+ * @since 3.1.0
+ * @access public
+ *
+ * @param string $primary_table
+ * @param string $primary_id_column
+ * @return array
+ */
+ function get_sql( $primary_table, $primary_id_column ) {
+ global $wpdb;
+
+ $join = '';
+ $where = array();
+ $i = 0;
+
+ foreach ( $this->queries as $query ) {
+ extract( $query );
+
+ if ( ! taxonomy_exists( $taxonomy ) )
+ return array( 'join' => '', 'where' => ' AND 0 = 1');
+
+ $terms = array_unique( (array) $terms );
+
+ if ( empty( $terms ) )
+ continue;
+
+ if ( is_taxonomy_hierarchical( $taxonomy ) && $include_children ) {
+ $this->_transform_terms( $terms, $taxonomy, $field, 'term_id' );
+
+ $children = array();
+ foreach ( $terms as $term ) {
+ $children = array_merge( $children, get_term_children( $term, $taxonomy ) );
+ $children[] = $term;
+ }
+ $terms = $children;
+
+ $this->_transform_terms( $terms, $taxonomy, 'term_id', 'term_taxonomy_id' );
+ }
+ else {
+ $this->_transform_terms( $terms, $taxonomy, $field, 'term_taxonomy_id' );
+ }
+
+ if ( 'IN' == $operator ) {
+
+ if ( empty( $terms ) ) {
+ if ( 'OR' == $this->relation )
+ continue;
+ else
+ return array( 'join' => '', 'where' => ' AND 0 = 1' );
+ }
+
+ $terms = implode( ',', $terms );
+
+ $alias = $i ? 'tt' . $i : $wpdb->term_relationships;
+
+ $join .= " INNER JOIN $wpdb->term_relationships";
+ $join .= $i ? " AS $alias" : '';
+ $join .= " ON ($primary_table.$primary_id_column = $alias.object_id)";
+
+ $where[] = "$alias.term_taxonomy_id $operator ($terms)";
+ } elseif ( 'NOT IN' == $operator ) {
+
+ if ( empty( $terms ) )
+ continue;
+
+ $terms = implode( ',', $terms );
+
+ $where[] = "$primary_table.$primary_id_column NOT IN (
+ SELECT object_id
+ FROM $wpdb->term_relationships
+ WHERE term_taxonomy_id IN ($terms)
+ )";
+ } elseif ( 'AND' == $operator ) {
+
+ if ( empty( $terms ) )
+ continue;
+
+ $num_terms = count( $terms );
+
+ $terms = implode( ',', $terms );
+
+ $where[] = "$primary_table.$primary_id_column IN (
+ SELECT object_id
+ FROM $wpdb->term_relationships
+ WHERE term_taxonomy_id IN ($terms)
+ GROUP BY object_id HAVING COUNT(object_id) = $num_terms
+ )";
+ }
+
+ $i++;
+ }
+
+ if ( !empty( $where ) )
+ $where = ' AND ( ' . implode( " $this->relation ", $where ) . ' )';
+ else
+ $where = '';
+
+ return compact( 'join', 'where' );
+ }
+
+ /**
+ * Transforms a list of terms, from one field to another.
+ *
+ * @since 3.1.0
+ * @access private
+ *
+ * @param array &$terms The list of terms
+ * @param string $taxonomy The taxonomy of the terms
+ * @param string $field The initial field
+ * @param string $resulting_field The resulting field
+ */
+ function _transform_terms( &$terms, $taxonomy, $field, $resulting_field ) {
+ global $wpdb;
+
+ if ( empty( $terms ) )
+ return;
+
+ if ( $field == $resulting_field )
+ return;
+
+ $resulting_field = esc_sql( $resulting_field );
+
+ switch ( $field ) {
+ case 'slug':
+ case 'name':
+ $terms = "'" . implode( "','", array_map( 'sanitize_title_for_query', $terms ) ) . "'";
+ $terms = $wpdb->get_col( "
+ SELECT $wpdb->term_taxonomy.$resulting_field
+ FROM $wpdb->term_taxonomy
+ INNER JOIN $wpdb->terms USING (term_id)
+ WHERE taxonomy = '$taxonomy'
+ AND $wpdb->terms.$field IN ($terms)
+ " );
+ break;
+
+ default:
+ $terms = implode( ',', array_map( 'intval', $terms ) );
+ $terms = $wpdb->get_col( "
+ SELECT $resulting_field
+ FROM $wpdb->term_taxonomy
+ WHERE taxonomy = '$taxonomy'
+ AND term_id IN ($terms)
+ " );
+ }
+ }
+}
+