Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
class-wp-tax-query - correction of operator AND handling
public function get_sql_for_clause( &$clause, $parent_query ) {
global $wpdb;
$sql = array(
'where' => array(),
'join' => array(),
);
$join = $where = '';
// BF: Buffer a non-referential copy of the input parameters before clean_query is called. For use when operator == AND
$input_clause = $clause;
// BF: Skip this step when AND
$operator = strtoupper( $clause['operator'] );
if ( 'AND' != $operator ) {
$this->clean_query( $clause );
}
if ( is_wp_error( $clause ) ) {
return self::$no_results;
}
$terms = $clause['terms'];
if ( 'IN' == $operator ) {
if ( empty( $terms ) ) {
return self::$no_results;
}
$terms = implode( ',', $terms );
/*
* Before creating another table join, see if this clause has a
* sibling with an existing join that can be shared.
*/
$alias = $this->find_compatible_table_alias( $clause, $parent_query );
if ( false === $alias ) {
$i = count( $this->table_aliases );
$alias = $i ? 'tt' . $i : $wpdb->term_relationships;
// Store the alias as part of a flat array to build future iterators.
$this->table_aliases[] = $alias;
// Store the alias with this clause, so later siblings can use it.
$clause['alias'] = $alias;
$join .= " LEFT JOIN $wpdb->term_relationships";
$join .= $i ? " AS $alias" : '';
$join .= " ON ($this->primary_table.$this->primary_id_column = $alias.object_id)";
}
$where = "$alias.term_taxonomy_id $operator ($terms)";
} elseif ( 'NOT IN' == $operator ) {
if ( empty( $terms ) ) {
return $sql;
}
$terms = implode( ',', $terms );
$where = "$this->primary_table.$this->primary_id_column NOT IN (
SELECT object_id
FROM $wpdb->term_relationships
WHERE term_taxonomy_id IN ($terms)
)";
} elseif ( 'AND' == $operator ) {
if ( empty( $terms ) ) {
return $sql;
}
$input_terms = $input_clause['terms'];
$num_terms = count( $input_terms );
// BF: Loop each individual term...
$term_index = 0;
foreach ($terms as &$term) {
//BF: Be sure to use a non-manipulated query object to avoid double transformations of fields
$term_clause = $input_clause;
// Clean each term separately i.e. get children for each term separately
$this->clean_query( $term_clause, $term_index, $expanded_term);
$expanded_term = implode(',', $expanded_term);
$where .= $term_index > 0 ? ' AND ' : '';
$where .= "EXISTS (
SELECT 1
FROM $wpdb->term_relationships
WHERE term_taxonomy_id IN ($expanded_term)
AND object_id = $this->primary_table.$this->primary_id_column
)";
//BF: Reset $expanded_term before looping through next term...
$expanded_term = [];
$term_index++;
}
} elseif ( 'AND_ORIGINAL' == $operator ) {
if ( empty( $terms ) ) {
return $sql;
}
$num_terms = count( $terms );
$terms = implode( ',', $terms );
$where = "(
SELECT COUNT(1)
FROM $wpdb->term_relationships
WHERE term_taxonomy_id IN ($terms)
AND object_id = $this->primary_table.$this->primary_id_column
) = $num_terms";
} elseif ( 'NOT EXISTS' === $operator || 'EXISTS' === $operator ) {
$where = $wpdb->prepare( "$operator (
SELECT 1
FROM $wpdb->term_relationships
INNER JOIN $wpdb->term_taxonomy
ON $wpdb->term_taxonomy.term_taxonomy_id = $wpdb->term_relationships.term_taxonomy_id
WHERE $wpdb->term_taxonomy.taxonomy = %s
AND $wpdb->term_relationships.object_id = $this->primary_table.$this->primary_id_column
)", $clause['taxonomy'] );
}
$sql['join'][] = $join;
$sql['where'][] = $where;
return $sql;
}
/**
* Validates a single query.
*
* BF: ADDED HANDLING OF SINGLE TERMS. This function also gets hold of any term children.
* DEFAULT: Handle the array of terms in query.
*
* @since 3.2.0
* @access private
*
* @param array $query The single query. Passed by reference.
* @param int $term_index The index of a specific term to be processed. Default = -1 //BF: Added
* @param array $expanded_term The set of the specific term and its children. Default = [] //BF: Added
*/
private function clean_query( &$query, $term_index = -1, &$expanded_term = [] ) {
if ( empty( $query['taxonomy'] ) ) {
if ( 'term_taxonomy_id' !== $query['field'] ) {
$query = new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
return;
}
// so long as there are shared terms, include_children requires that a taxonomy is set
$query['include_children'] = false;
} elseif ( ! taxonomy_exists( $query['taxonomy'] ) ) {
$query = new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
return;
}
$query['terms'] = array_unique( (array) $query['terms'] );
if ( is_taxonomy_hierarchical( $query['taxonomy'] ) && $query['include_children'] ) {
$this->transform_query( $query, 'term_id' );
if ( is_wp_error( $query ) )
return;
$children = array();
//BF: Individual handling if a specific term is input (term_index > -1)
if ( $term_index > -1 ) {
$term = $query['terms'][$term_index];
$children = array_merge( $children, get_term_children( $term, $query['taxonomy'] ) );
$children[] = $term;
$expanded_term = $children;
$this->transform_query( $query, 'term_taxonomy_id', $expanded_term );
}
//BF: Or handle all the terms contained in the query
else {
foreach ( $query['terms'] as $term ) {
$children = array_merge( $children, get_term_children( $term, $query['taxonomy'] ) );
$children[] = $term;
}
$query['terms'] = $children;
$this->transform_query( $query, 'term_taxonomy_id' );
}
}
}
/**
* Transforms a single query, from one field to another.
*
* BF: ADDED HANDLING of another set of terms than the one contained in the query itself.
*
* @since 3.2.0
*
* @global wpdb $wpdb The WordPress database abstraction object.
*
* @param array $query The single query. Passed by reference.
* @param string $resulting_field The resulting field. Accepts 'slug', 'name', 'term_taxonomy_id',
* or 'term_id'. Default 'term_id'.
* @param array $input_terms An array of explicit terms. Passed by reference. If set then this is used instead of the query['terms']. Defaults to array[].
*/
public function transform_query( &$query, $resulting_field, &$input_terms = [] ) {
global $wpdb;
if ( empty( $query['terms'] ) )
return;
if ( $query['field'] == $resulting_field ) {
return;
}
$resulting_field = sanitize_key( $resulting_field );
//BF: Evaluate and set which terms to be used
if ( !empty($input_terms) ) {
$active_terms = $input_terms;
}
else {
$active_terms = $query['terms'];
}
switch ( $query['field'] ) {
case 'slug':
case 'name':
//BF: Loop the given terms ($active_terms)
foreach ( $active_terms as &$term ) {
/*
* 0 is the $term_id parameter. We don't have a term ID yet, but it doesn't
* matter because `sanitize_term_field()` ignores the $term_id param when the
* context is 'db'.
*/
$term = "'" . esc_sql( sanitize_term_field( $query['field'], $term, 0, $query['taxonomy'], 'db' ) ) . "'";
}
// BF: Implode the given terms ($active_terms)
$terms = implode( ",", $active_terms );
$terms = $wpdb->get_col( "
SELECT $wpdb->term_taxonomy.$resulting_field
FROM $wpdb->term_taxonomy
INNER JOIN $wpdb->terms USING (term_id)
WHERE taxonomy = '{$query['taxonomy']}'
AND $wpdb->terms.{$query['field']} IN ($terms)
" );
break;
case 'term_taxonomy_id':
//BF: Use the given terms ($active_terms)
$terms = implode( ',', array_map( 'intval', $active_terms ) );
$terms = $wpdb->get_col( "
SELECT $resulting_field
FROM $wpdb->term_taxonomy
WHERE term_taxonomy_id IN ($terms)
" );
break;
default:
//BF: Use the given terms ($active_terms)
$terms = implode( ',', array_map( 'intval', $active_terms ) );
$terms = $wpdb->get_col( "
SELECT $resulting_field
FROM $wpdb->term_taxonomy
WHERE taxonomy = '{$query['taxonomy']}'
AND term_id IN ($terms)
" );
}
//BF: Use the given terms ($active_terms)
if ( 'AND' == $query['operator'] && count( $terms ) < count( $active_terms ) ) {
$query = new WP_Error( 'inexistent_terms', __( 'Inexistent terms.' ) );
return;
}
//BF: Decide whether to set $input_terms or $query['terms'] with the transformed terms
if ( !empty($input_terms) ) {
$input_terms = $terms;
}
else {
$query['terms'] = $terms;
}
$query['field'] = $resulting_field;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.