5

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?

  • Would converting it to a slug work? I was thinking sanitize_title() but you mentioned that you need to it for search so I wasn't sure. – Howdy_McGee Mar 9 '15 at 20:51
6

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

  • Thank you for your in-depth analysis on this Pieter. It was incredibly helpful, and corresponds with everything I was experiencing. In the end, I went with a very similar workaround to what you mentioned. A helper function loops thru all of the taxonomies that are used in search suggestions, then returns the ID for the term so that I can construct the WP_Query request. While this works, I hate that I have the overhead of this looping construct. It will work for now, but will certainly be revisited in the future. – Philip Downer Mar 11 '15 at 14:40
  • My pleasure. Wordpress is stuck in php 5.2, so all the proper php functions of newer versions is not yet available. I am thinking of a proper fix that I would like to submit, just not sure yet how it will look and if it will be accepted as a fix. Will keep you up to date of future developments. Enjoy :-) – Pieter Goosen Mar 11 '15 at 14:45
  • If it helps, my searching through Trac revealed Changeset 31346. I'll be interested in future development. Thanks again! – Philip Downer Mar 11 '15 at 14:50
  • Great, this is perfect, lets hope his gets fixed in future releases. Will add that in my answer for future reference for others – Pieter Goosen Mar 11 '15 at 14:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.