1

I've got a custom post type called course. Within a course post, I have a number of custom fields, specifically (all AFC select dropdowns): course_or_project, time_to_complete_project, time_to_complete_course and difficulty. I also have a taxonomy called course_project_category.

My aim is to be able to create a number of dropdown/sliders, build a URL by reading the values of said dropdowns/sliders and on click of a search button, use the query string I've put together (jQuery) to filter just the right posts. This functionality is all working. Well... sometimes the results are a little confused i.e. only filtering by some of the parameters.

On to the code.

I've added a function in to expose my custom fields:

function my_pre_get_posts( $query ) {

// do not modify queries in the admin
if( is_admin() ) {
    return $query;
}

// only modify queries for 'course' post type
if( isset($query->query_vars['post_type']) && $query->query_vars['post_type'] == 'course' ) {

    // allow the url to alter the query
    if( isset($_GET['course_or_project']) ) {
        $query->set('meta_key', 'course_or_project');
        $query->set('meta_value', $_GET['course_or_project']);
    } 

    if( isset($_GET['time_to_complete_project']) ) {
        $query->set('meta_key', 'time_to_complete_project');
        $query->set('meta_value', $_GET['time_to_complete_project']);
    } 

    if( isset($_GET['time_to_complete_course']) ) {
        $query->set('meta_key', 'time_to_complete_course');
        $query->set('meta_value', $_GET['time_to_complete_course']);    
    } 

    if( isset($_GET['difficulty']) ) {    
        $query->set('meta_key', 'difficulty');
        $query->set('meta_value', $_GET['difficulty']);
    } 

}

// return
return $query;

}

add_action('pre_get_posts', 'my_pre_get_posts');

Querying the taxonomy appears to work out-of-the-box.

So an example of my completed URL might be:

http://localhost:3000/courses/?course_project_category=jokes&course_or_project=project&difficulty=easy&time_to_complete_project=15

My results are a mixed bag at this point.

From the above query, I've returned 2 posts which mostly match my criteria but as a specific example, one post has a time_to_complete_project value as 30 even though the query specified 15.

I cannot figure this out. I can see the parameters are all there as expected through Chrome dev tools:

enter image description here

Any ideas?!

Update

Here's a new approach I have that is working (but is lacking validation and sanitation):

// array of filters (field key => field name)
    $GLOBALS['my_query_filters'] = array( 
        'field_1'   => 'course_or_project', 
        'field_2'   => 'difficulty',
        'field_3'   => 'time_to_complete_project',
        'field_4'   => 'time_to_complete_course'
    );
// action

add_action('pre_get_posts', 'my_pre_get_posts', 10, 1);

function my_pre_get_posts( $query ) {

    // bail early if is in admin
    if( is_admin() ) {

        return;

    }

    if( isset($query->query_vars['post_type']) && $query->query_vars['post_type'] == 'course' ) {

        // get meta query
        $meta_query = $query->get('meta_query');


        // loop over filters
        foreach( $GLOBALS['my_query_filters'] as $key => $name ) {

            // continue if not found in url
            if( empty($_GET[ $name ]) ) {

                continue;

            }


            // get the value for this filter
            // eg: http://www.website.com/events?city=melbourne,sydney
            $value = explode(',', $_GET[ $name ]);


            // append meta query
            $meta_query[] = array(
                'key'       => $name,
                'value'     => $value,
                'compare'   => 'IN',
            );

        } 


        // update meta query
        $query->set('meta_query', $meta_query);

    }

}
1
  • 1
    You need to build a proper meta_query. Your code will never work if you have more than one meta_key. Also, you should never ever use unsanitized and non validated values from a $_GET variable (or any other type of input) Nov 11, 2015 at 18:26

2 Answers 2

1

You need to build a proper meta_query. Your code will only work when you have one meta_key set, if you have more than 1, everything will be haywire.

Also note, you should never ever use any unsanitized, non validated values from a $_GET variable (and for that matter any value coming from anywhere). It is extremely easy to inject malicious code into your site through adding some script in your URL. Validation and sanitation does have a very very small impact on performance, but definitely worth the while.

To solve your issue, lets try to recode your action. I have commented where needed.

add_action( 'pre_get_posts', function ( $q )
{
    if (    !is_admin() // Do this only on the front end
         && $q->is_main_query() // Targets the main query only
    ) {

        if( isset($query->query_vars['post_type']) && $query->query_vars['post_type'] == 'course' ) { // Not sure about this, can be $q->is_post_type_archive( 'course' )
            // Get all our $_GET variables and sanitize and validate them
            $course_or_project        = filter_input( INPUT_GET, 'course_or_project',        FILTER_SANITIZE_STRING );
            $time_to_complete_project = filter_input( INPUT_GET, 'time_to_complete_project', FILTER_SANITIZE_STRING ); 
            $time_to_complete_course  = filter_input( INPUT_GET, 'time_to_complete_course',  FILTER_SANITIZE_STRING );
            $difficulty               = filter_input( INPUT_GET, 'difficulty',               FILTER_VALIDATE_INT    ); 

            // Set our variable to hold the meta_query
            $meta_query = [];

            // Now we build our meta_query
            if( $course_or_project ) {
                $meta_query[] = [
                    'key'   => 'course_or_project',
                    'value' => $course_or_project
                ];
            } 

            if( $time_to_complete_project ) {
                $meta_query[] = [
                    'key'   => 'time_to_complete_project',
                    'value' => $time_to_complete_project
                ];
            } 

            if( $time_to_complete_course ) {
                $meta_query[] = [
                    'key'   => 'time_to_complete_course',
                    'value' => $time_to_complete_course    
                ];
            } 

            if( $difficulty ) {
                $meta_query[] = [
                'key'   => 'difficulty',
                'value' => $difficulty
                ];
            }

            // Make sure we have something in $meta_query before setting it
            if ( $meta_query ) 
                $q->set( 'meta_query', $meta_query );

        }
    }
});

You can refine the query as needed, but this should be the very basic

6
  • Please see updated code, had a slight syntax issue ;-) Nov 12, 2015 at 0:54
  • Hi Pieter. Thanks for the detailed, informative answer complete with code! I've learnt something new in validation and sanitation and I can completely see why that is so important. I am having some issues with the code, which is most likely my fault! The taxonomy parameters work as expected but the custom field-based ones are not. I have a feeling that it's due to the part you commented "Not sure about this, can be $q->is_post_type_archive( 'course' )". That line did work previously but perhaps as this is a different approach it no longer applies? How should your suggested replacement fit in?
    – lotech
    Nov 12, 2015 at 9:33
  • I will need to check and test this. In the mean time, try to copy just the important parts in my code into yours and see how it works. I will only be able to test everything later Nov 12, 2015 at 10:05
  • Hi Pieter. I did give it a go with no luck but I've only tinkered a bit. I've also found (by using some of your terminology, this AFC article on doing just this: advancedcustomfields.com/resources/…). However, I notice there is no validation or sanitation. I have, however, got a completely working example through basing my code on that example. I'll update my original question with the code I have working (without the validation or sanitation. Thanks again for the help.
    – lotech
    Nov 12, 2015 at 10:24
  • My pleasure, you can use the same logic I have used in my answer to sanitize and validate your $_GET values. You should post your solution as an answer and accept your own answer as your solution solves the issue ;-) Nov 12, 2015 at 10:50
1

OK, here's what I got working in the end. I'm honestly not sure if I've sanitised the parameters correctly so input welcome there! Other than that, all works as intended. Big thanks to Pieter Goosen!

// action
add_action('pre_get_posts', 'my_pre_get_posts', 10, 1);

function my_pre_get_posts( $query ) {

    // bail early if is in admin
    if( is_admin() ) {
        return;
    }

    if( isset($query->query_vars['post_type']) && $query->query_vars['post_type'] == 'course' ) {

        // array of filters (field key => field name)
        $GLOBALS['my_query_filters'] = array( 
            'field_1'   => 'course_or_project', 
            'field_2'   => 'difficulty',
            'field_3'   => 'time_to_complete_project',
            'field_4'   => 'time_to_complete_course'
        );

        // get meta query
        $meta_query = $query->get('meta_query');

        // loop over filters
        foreach( $GLOBALS['my_query_filters'] as $key => $name ) {

            // sanitize parameter
            $name = filter_input( INPUT_GET, $name, FILTER_SANITIZE_STRING );

            // continue if not found in url
            if( empty($_GET[ $name ]) ) {
                continue;
            }

            // get the value for this filter
            // eg: http://www.website.com/events?city=melbourne,sydney
            $value = explode(',', $_GET[ $name ]);

            // append meta query
            $meta_query[] = array(
                'key'       => $name,
                'value'     => $value,
            );

        } 

        // update meta query
        $query->set('meta_query', $meta_query);

    }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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