Escaping WP_Query tax_query when term has special character(s)

Here’s my issue. I have a custom post type that I’m attaching a custom taxonomy to. This taxonomy will allow the admin to provide search suggestions that will ensure the post is found when a user searches. This taxonomy is also used on the front-end to autocomplete the search field with these suggestions.

The system works fine, except when the term contains a forward slash, a space or other “special” characters.

<?php
$keyword = 'Ski-in%2FSki-out';//Submitted via $_GET
$keyword = urldecode($keyword); // (string)'Ski-in/Ski-out'

$taxQuery = new WP_Query(array(
    'post_type' => 'ml_properties',
    'tax_query' => array(
        'relation' => 'OR',
        array(
            'taxonomy' => 'mc_tax_lifestyle',
            'field' => 'name',
            'terms' => array($keyword),
            'operator' => 'IN'
        )
    )
));
?>

I’ve looked into using the escaping features provided by WordPress, but haven’t had any luck there. I thought this would be a simple problem, but either my Google-fu is not strong, or I’m just not having any luck.

I’ve also verified that that Ski-in/Ski-out is the exact string stored in MySQL. I’ve looked into using esc_attr(), $wpdb->esc_like() and esc_sql() but none have an effect.

Perhaps I’m missing something obvious?

1
1

I’m not sure if this is a bug, but it need further investigation. I’ve run a few quick tests on the name field in a tax_query, and whenever a term name has got a special character or have more than one word, the tax_query is excluded from the SQL query

TEST 1

I have use two terms here, your term Ski-in/Ski-out and one of the terms on my test site Uit die koskas. Now, if I run my custom query as follows

$taxQuery = new \WP_Query(array(
    'post_type' => 'post',
    'tax_query' => array(
        array(
            'taxonomy' => 'category',
            'field' => 'name',
            'terms' => 'Ski-in/Ski-out',
            'operator' => 'IN'
        )
    )
));
?><pre><?php var_dump($taxQuery->request); ?></pre><?php    

the var_dump() of the request gives me this

string(254) "SELECT SQL_CALC_FOUND_ROWS  wp_posts.ID 
FROM wp_posts  
WHERE 1=1  
AND ( 
      0 = 1
    ) 
AND wp_posts.post_type="post" 
AND (wp_posts.post_status="publish" 
OR wp_posts.post_status="private") 
GROUP BY wp_posts.ID 
ORDER BY wp_posts.post_date DESC 
LIMIT 0, 4"

The tax_query is not appended to the SQL query

TEST 2

If you take a single word term with no special character, the query works. Here I’ve tested with a term called Ongekategoriseerd

$taxQuery = new \WP_Query(array(
    'post_type' => 'post',
    'tax_query' => array(
        array(
            'taxonomy' => 'category',
            'field' => 'name',
            'terms' => 'Ongekategoriseerd',
            'operator' => 'IN'
        )
    )
));
?><pre><?php var_dump($taxQuery->request); ?></pre><?php

This gives me the correct SQL query

string(378) "SELECT SQL_CALC_FOUND_ROWS  wp_posts.ID FROM wp_posts  
INNER JOIN wp_term_relationships 
ON (wp_posts.ID = wp_term_relationships.object_id) 
WHERE 1=1  
AND ( 
      wp_term_relationships.term_taxonomy_id 
IN (1)
    ) 
AND wp_posts.post_type="post" 
AND (wp_posts.post_status="publish" 
OR wp_posts.post_status="private") 
GROUP BY wp_posts.ID 
ORDER BY wp_posts.post_date DESC 
LIMIT 0, 4" 

I’m not yet sure if this is intentional or a bug, but in the meantime you can have a look at the WP_Query source code and see how the tax_query is build. I will look into it as well in the near future

UPDATE 1

I quickly had a look through the WP_Query class before leaving for work. Towards the end of the class, WP_Query does some backward compatibility tests, and from eye value it might seem that this is where everything fails which in turn fails in appending the join clausse to the SQL query.

I cannot find a trac ticket about this issue, if there are one and anyone has a link, feel free to update my answer or post in comments.

WORKAROUND

If you need to and have to use the term name, you should create yourself a helper function using get_term_by(). You can then use the term name to get the term object and use the term ID from there to use in a tax_query

UPDATE 2

FOUND IT

The problem was not in WP_Query itself (what a mess looking through these classes). WP_Query uses WP_Tax_Query to construct the tax_query. Check the last line just before the do_action call in the parse_tax_query method in WP_Query

$this->tax_query = new WP_Tax_Query( $tax_query );

Great, moving to the WP_Tax_Query class. This class have the following method, transform_query which transforms a single query from one field to another. This is where everything breaks when you set your field parameter to name

With the field set to name, the name is sanitized using sanitize_title_for_query

$terms = "'" . implode( "','", array_map( 'sanitize_title_for_query', $query['terms'] ) ) . "'";

This removes slashes and convert empty spaces into hypens. This means Ski-in/Ski-out is converted into ski-inski-out and Uit die koskas is converted to uit-die-koskas. Because your term name is invalid, the following query which gets the term id and its children,

$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)
" );

fails and returns an empty array

array(0) {
}

CONCLUSION

IMHO, the sanitation here is wrong and should be replaced with a more appropriate method which allows for single spaces and slashes. What is purpose of having a name field if you can’t use term names properly. This sanitation kills the usage of the name field in a tax_query in the above mentioned cases.

As I already stated before, maybe the best method if you need to use term names is to create that helper function where you use get_term_by() to get the ID from a term and then use that ID in your tax_query

UPDATE 3

Thanks to @manifestphil in comments, there is a changeset #31346 out on this exact crazy over-sanitizing issue. Lets hope this get fixed in future releases

Leave a Comment