WordPress.org

Support

Support » Plugins and Hacks » Hacks » [Resolved] Category sidebar seach: checkbox and custom fields problem

[Resolved] Category sidebar seach: checkbox and custom fields problem

  • Hi,

    I’m struggling with the following problem for a while. It’s almost working, but still not yet. First of all, sorry for my bad English.

    I made this sidebar search form (see code below) for my category page to filter the search results based on the tags which are available within the category.

    The first part is working great when I use radio buttons, but I want the form to remember mulitple checkbox fields. Someone suggested name="tag[]" but that’s not working unfortunately. When I use name="tag" the url output is /?tag=test&tag=test2 instead of /?tag=test,test2 so it won’t work properly.

    The next problem. I use this function to make a url parameter based on a custom field, but I can’t figure out how to use it with multiple keys.

    function wpa_filter_home_query( $query ){
        if( $query->is_main_query()
        && isset( $_GET['provincie'] ) ) {
            $meta_query = array(
                array(
                    'key' => 'provincie',
                    'value' => array( $_GET['provincie'])
                )
            );
            $query->set( 'meta_query', $meta_query );
        }
    }
    add_action( 'pre_get_posts', 'wpa_filter_home_query' );

    And the last problem is that I want to show only keys from custom fields under <h3>Land</h3> and <h3>Regio</h3> just like the code only shows tags which are used. I also can’t figure out how to let this work for the custom fields.

    The form:

    <form name="tags" onChange="document.forms.tags.submit();">
    		<?php
    		if (is_category()){
    		  $cat = get_query_var('cat');
    		  $yourcat = get_category ($cat);
    		}
    		$tag_IDs = array();
    		query_posts('category_name='.$yourcat->slug);
    		if (have_posts()) : while (have_posts()) : the_post();
    		  $posttags = get_the_tags();
    		  if ($posttags):
    			foreach($posttags as $tag) {
    			if (!in_array($tag->term_id , $tag_IDs)):
    			   $tag_IDs[] = $tag->term_id;
    			   $tag_names[$tag->term_id] = $tag->name;
    			   $tag_slug[$tag->term_id] = $tag->slug;
    			 endif;
    			}
    		  endif;
    		endwhile; endif;
    		wp_reset_query();
    
    		if (!empty($tag_IDs)){
    			echo '<h3>Het meest geschikt voor</h3>';
    			echo "<input type=\"checkbox\" name=\"tag\" value=\"\"> Show all<br>";
    		}
    		foreach($tag_IDs as $tag_ID){
    			$checked = $tag_slug[$tag_ID];
    			echo '<input type="checkbox" name="tag" value="'.$checked.'"' ;
    			if((isset($_GET["tag"])) && $_GET["tag"] == $checked) {
    			echo ' checked="checked"';
    			}
    			echo '> '.$tag_names[$tag_ID].'<br>';
    		}
    		?>
    
    		<div class="margin25px"></div>
    
    		<h3>Land</h3>
    			<input type="radio" name="land" value=""> Alles weergeven<br>
    			<input type="radio" name="land" value="nederland" <?php if((isset($_GET["land"])) && $_GET["land"] == "nederland") { echo "checked";}?>> Nederland<br>
    			<input type="radio" name="land" value="belgie" <?php if((isset($_GET["land"])) && $_GET["land"] == "belgie") { echo "checked";}?>> Belgie
    
    		<div class="margin25px"></div>
    
    		<h3>Regio</h3>
    			<input type="radio" name="provincie" value=""> Alles weergeven<br>
    			<input type="radio" name="provincie" value="gelderland" <?php if((isset($_GET["provincie"])) && $_GET["provincie"] == "gelderland") { echo "checked";}?>> Gelderland<br>
    			<input type="radio" name="provincie" value="overijssel" <?php if((isset($_GET["provincie"])) && $_GET["provincie"] == "overijssel") { echo "checked";}?>> Overijssel
    		</form>

    Hopefully someone can give me some hints to make this work. It’s a great category search form if it’s working properly. Sorry if I ask to many questions, but it’s all related to each other since it’s all meant to make the sidebar filter function work.

Viewing 15 replies - 1 through 15 (of 28 total)
  • bcworkz
    Participant

    @bcworkz

    Hello my friend! So many questions today 🙂 Er, yesterday by now.

    To get multiple checkbox values, you must use ‘[]’ as the final part of the name, that was good advice. I don’t know why it’s not working, but the ‘[]’ must occur for there to be any hope of working.

    If the checkboxes are all named “tag[]” and the form method is GET, the URL parameters will be something like ?tag[]=test&tag[]=test2. There is not a way to get forms to structure the request as you suggest like ?tag=test,test2. Though the URL parameter names include the ‘[]’ portion, they are dropped in the $_GET key. You end up with something equivalent to $_GET['tag'] = array('test', 'test2',);. This is the way it happens, your code needs to work with it.

    How you build a query from checkbox values depends if the values are to be related with AND or OR. If OR, set the ‘value’ as the $_GET['tag'] array and set the ‘compare’ as ‘IN’. If AND, you must step through the checkbox values with foreach(){} and build an array of arrays for ‘meta_query’ where each ‘value’ is a single checkbox value so all the arrays together have a relation of ‘AND’.

    There is no easy answer to get custom fields that are actually used because they are in post_meta and not part of a taxonomy like tags and categories. You need to query for all candidate posts from which to check for fields. Then step through the posts and and accumulate unique fields into an array. You can use if ( !in_array( $value, $fields )) $fields[] = $value; to only add values that do not currently exist so that all values are unique.

    I hope that answers your questions, if not, you know the routine 🙂

    Thanks for your help again!

    If I use this code:

    foreach($tag_IDs as $tag_ID){
    			$checked = $tag_slug[$tag_ID];
    			echo '<input type="checkbox" name="tag[]" value="'.$checked.'"' ;
    			if((isset($_GET["tag"])) && $_GET["tag"] == $checked) {
    			echo ' checked="checked"';
    			}
    			echo '> '.$tag_names[$tag_ID].'<br>';
    		}

    I get this url paramter:

    /?tag%5B%5D=test

    and then there’re no search results. Also the checkboxes aren’t checked anymore.

    When I have more time I’ll look after your other answers and post my results here.

    I think I have the solution for the creating of the url paramter based on multiple custom fields. It looks like this code is working fine:

    function wpa_filter_home_query( $query ){
        if( $query->is_main_query()
    	&& isset( $_GET['land'] )
        && isset( $_GET['provincie'] ) ) {
            $meta_query = array(
                array(
                    'key' => 'land',
                    'value' => array( $_GET['land'])
                ),
    			array(
                    'key' => 'provincie',
                    'value' => array( $_GET['provincie'])
                )
            );
            $query->set( 'meta_query', $meta_query );
        }
    }
    add_action( 'pre_get_posts', 'wpa_filter_home_query' );

    It’s another approach then you suggested, but I thought I’ll give it a try.

    bcworkz
    Participant

    @bcworkz

    Sure, anything is worth a try 🙂

    My problem is I don’t know exactly how you want the checkbox selections to influence the query. If your last example represents what you want, then that is what you need to use. What it does is only return posts with both land and provincie terms, but the returned posts can have any of the checked terms assigned. I can imagine this is very likely what you want, so kudos for developing that!

    I see I didn’t test it well enough. It looks like it only works when “land” and “provincie” are both checked, just like you said. Then it is filtering the post whith a specific “land” AND “provincie” (also when a tag is checked). But when I only check “land” or “provincie” (even when a tag is checked) it isn’t filtering (only the checked tag is).

    So, if I check one tag it works. But if I only check “land” or “provincie” the query isn’t filtering. I need it to work all together (that is already working), but also everything separately.

    'compare' => 'IN' isn’t working either. I’m not sure how to use the AND function yet, but I’ll give it a try.

    This it how it looks like (without any checkes): http://oi58.tinypic.com/2wp05km.jpg.
    The url parameter then is (if I check it): /?tag=test&land=nederland&provincie=overijssel (this works).

    But when it is /?land=nederland OR /?provincie=gelderland it isn’t working.

    So, when “land” and “provincie” are filtering independently it should work, but right now they’re filtering when one another or a tag is checked.

    With one array it works when I click on “provincie” without the need of selecting something else.

    function wpa_filter_home_query( $query ){
        if( $query->is_main_query()
        && isset( $_GET['provincie'] ) ) {
            $meta_query = array(
    			array(
                    'key' => 'provincie',
                    'value' => array( $_GET['provincie'])
                )
            );
            $query->set( 'meta_query', $meta_query );
        }
    }
    add_action( 'pre_get_posts', 'wpa_filter_home_query' );

    About the query from checkbox values: I figured out the relation should be AND. I didn’t know exactly what you mean with “If AND, you must step through the checkbox values with foreach(){} and build an array of arrays for ‘meta_query’ where each ‘value’ is a single checkbox value so all the arrays together have a relation of ‘AND’.”

    So, after much struggle I came with my own alternate solution:

    <?php
    function wpa_filter_home_query( $query ){
        if( $query->is_main_query()
        && isset( $_GET['land'] )
    	&& isset( $_GET['provincie'] ) ) {
            $meta_query = array(
    		'relation' => 'AND',
    			array(
                    'key' => 'land',
                    'value' => array( $_GET['land'] )
                ),
    			array(
                    'key' => 'provincie',
                    'value' => array( $_GET['provincie'] )
                )
            );
            $query->set( 'meta_query', $meta_query );
        }
    	elseif( $query->is_main_query()
    	&& isset( $_GET['land'] ) ) {
            $meta_query = array(
    			array(
                    'key' => 'land',
                    'value' => array( $_GET['land'] )
                )
            );
            $query->set( 'meta_query', $meta_query );
        }
    	elseif( $query->is_main_query()
    	&& isset( $_GET['provincie'] ) ) {
            $meta_query = array(
    			array(
                    'key' => 'provincie',
                    'value' => array( $_GET['provincie'] )
                )
            );
            $query->set( 'meta_query', $meta_query );
        }
    }
    add_action( 'pre_get_posts', 'wpa_filter_home_query' );
    ?>

    I’m not sure why it’s working, but it looks like it is working alright. Is this a proper solution or shouldn’t I use it in this way?
    With this code I can also filter on “land” and “provincie” separately.

    If it’s alright, then my next problem is that I want to remove the parameter /?land= if I check “Show All”. When there’s /?land= in the parameter it isn’t filtering properly.

    bcworkz
    Participant

    @bcworkz

    Sorry to have been ignoring you for the past few days, I’ve been away.

    What I meant by the phrase you quoted only applies if you want posts where ALL the checked terms for a taxonomy are ALL assigned to every post returned. Since this does not sound like what you want, I will not try to explain further unless you think it’s important.

    What you ended up with was exactly what I had in mind while reading your posts from a few days back, so good work figuring that out on your own. Sorry it was such a struggle, but you probably learned more than if I had just told you what to do 🙂

    I’m not quite sure what the problem with /?land= is with “Show All” but the solution is probably yet another if/elseif condition when “Show All” is checked that builds a query that essentially skips over the meta_query portion entirely, unless maybe you need to require that something be assigned to avoid returning unrelated posts.

    Hi,

    No problem and I hope you had a nice couple of days off 🙂

    If /?land= is in the url parameter then zero results are given instead of all the results, so when I click the “Show All” radio button then I need the parameter /?land= to disappear. I’ll try what you suggested.

    In the meanwhile another problem occurs 🙁
    When I’m on page 2 in the category (/page/2/) and I check something a 404 error appears. I found out I need to add the “paged” parameter to the query.
    I thought to do it like this:

    function wpa_filter_home_query( $query ){
    	$paged = ( get_query_var('paged') ) ? get_query_var('paged') : 1;
    	if( $query->is_main_query()
        && isset( $_GET['land'] )
    	&& isset( $_GET['provincie'] ) ) {
    		$meta_query = array(
    		'relation' => 'AND',
    			array(
                    'key' => 'land',
                    'value' => array( $_GET['land'] ),
    				'paged' => $paged
                ),
    			array(
                    'key' => 'provincie',
                    'value' => array( $_GET['provincie'] ),
    				'paged' => $paged
                )
            );
            $query->set( 'meta_query', $meta_query );

    But this still gives a 404 error on page 2 of the category.

    I also tried 'paged' => get_query_var( 'paged' ) but without succes.

    So the form is almost working as I want, but not on page 2, 3 and so on.

    bcworkz
    Participant

    @bcworkz

    Arrrgh! If it’s not one thing it’s another! Such is the nature of coding I suppose :/

    Apparently using query_posts() means you need to manage pagination as part of that query, WP only handles pagination for the original WP_Query object. I’m a little surprised using is_main_query() works for query_posts(), but I’ve seen similar behavior before.

    The thing is the usual symptom of the need to manage pagination is the first page is returned regardless of the page number requested, not returning a 404. So the problem may lie elsewhere. Just in case, the manage pagination issue is discussed on Making Custom Queries using Offset and Pagination.

    If you were to dump the query object you are modifying, I think you will find ‘paged’ is defined since it is in the request URL. What may be missing or erroneous is ‘posts_per_page’. Setting it from the value in the options table may be all that’s required.

    Failing that, the thing to do with unexpected 404s is to output the actual query string used from the ‘posts_request’ filter. Examining this usually reveals what the problem is. The tricky part is determining what to do to correct the query so it works properly. I might have an idea for what to do once we know what the problem with the query is.

    If you cannot decipher the query string, post it here and I’ll try to make sense of it.

    I tried the code from http://codex.wordpress.org/Making_Custom_Queries_using_Offset_and_Pagination as you suggested, but with no luck.

    In functions.php I added 'posts_per_page' => 9, but also without luck.

    The last thing I tried was adding this in functions.php:

    if ( $query->is_main_query()){
    		  $query->set('posts_per_page', 9);
    	  }

    But that isn’t working either. Everything is still giving 404 errors on page 2.

    This is my current code:

    In functions.php:

    /* Create URL parameter based on custom field */
    function wpa_filter_home_query( $query ){
    	if( $query->is_main_query()
        && isset( $_GET['land'] )
    	&& isset( $_GET['provincie'] ) ) {
    		$meta_query = array(
    		'relation' => 'AND',
    			array(
                    'key' => 'land',
                    'value' => array( $_GET['land'] )
                ),
    			array(
                    'key' => 'provincie',
                    'value' => array( $_GET['provincie'] )
                )
            );
            $query->set( 'meta_query', $meta_query );
        }
    	elseif( $query->is_main_query()
    	&& isset( $_GET['land'] ) ) {
            $meta_query = array(
    			array(
                    'key' => 'land',
                    'value' => array( $_GET['land'] )
                )
            );
            $query->set( 'meta_query', $meta_query );
        }
    	elseif( $query->is_main_query()
    	&& isset( $_GET['provincie'] ) ) {
            $meta_query = array(
    			array(
                    'key' => 'provincie',
                    'value' => array( $_GET['provincie'] )
                )
            );
            $query->set( 'meta_query', $meta_query );
        }
    }
    add_action( 'pre_get_posts', 'wpa_filter_home_query' );

    In the sidebar:

    <form name="tags" onChange="document.forms.tags.submit();">
    		<?php
    		if (is_category()){
    		  $cat = get_query_var('cat');
    		  $yourcat = get_category ($cat);
    		}
    		$tag_IDs = array();
    		query_posts('category_name='.$yourcat->slug);
    		if (have_posts()) : while (have_posts()) : the_post();
    		  $posttags = get_the_tags();
    		  if ($posttags):
    			foreach($posttags as $tag) {
    			if (!in_array($tag->term_id , $tag_IDs)):
    			   $tag_IDs[] = $tag->term_id;
    			   $tag_names[$tag->term_id] = $tag->name;
    			   $tag_slug[$tag->term_id] = $tag->slug;
    			 endif;
    			}
    		  endif;
    		endwhile; endif;
    		wp_reset_query();
    
    		if (!empty($tag_IDs)){
    			echo '<h3>Het meest geschikt voor</h3>';
    			echo "<input type=\"radio\" name=\"tag\" value=\"\"> Alles weergeven<br>";
    		}
    		foreach($tag_IDs as $tag_ID){
    			$checked = $tag_slug[$tag_ID];
    			echo '<input type="radio" name="tag" value="'.$checked.'"' ;
    			if((isset($_GET["tag"])) && $_GET["tag"] == $checked) {
    			echo ' checked="checked"';
    			}
    			echo '> '.$tag_names[$tag_ID].'<br>';
    		}
    		?>
    
    		<div class="margin25px"></div>
    
    		<h3>Land</h3>
    			<input type="radio" name="land" value=""> Alles weergeven<br>
    			<input type="radio" name="land" value="nederland" <?php if((isset($_GET["land"])) && $_GET["land"] == "nederland") { echo "checked";}?>> Nederland<br>
    			<input type="radio" name="land" value="belgie" <?php if((isset($_GET["land"])) && $_GET["land"] == "belgie") { echo "checked";}?>> Belgie
    
    		<div class="margin25px"></div>
    
    		<h3>Regio</h3>
    			<input type="radio" name="provincie" value=""> Alles weergeven<br>
    			<input type="radio" name="provincie" value="gelderland" <?php if((isset($_GET["provincie"])) && $_GET["provincie"] == "gelderland") { echo "checked";}?>> Gelderland<br>
    			<input type="radio" name="provincie" value="overijssel" <?php if((isset($_GET["provincie"])) && $_GET["provincie"] == "overijssel") { echo "checked";}?>> Overijssel
    		</form>

    The url on page one looks like this for example (which is working):
    /cat/activiteit/?tag=test-tag&land=nederland&provincie=overijssel

    The url on page two looks like this for example (which gives a 404 error):
    /cat/activiteit/page/2/?tag=test

    I’m very happy you take the time to help/teach me! I’m learning a lot here.

    bcworkz
    Participant

    @bcworkz

    I’m pleased you are happy, it makes me happy as well 🙂

    I have some what lost context of your setup over time, it is good you brought me up to date with the latest code. It is clear now that the 404 is related to the main query, so WP should be handling pagination for you, there should be no need for the work around suggested in the link I provided. I’m sorry I set you upon the wrong path with that.

    One problem I see is that the URL parameters for page 1 and page 2 do not match. Without the URL parameters, your ‘pre_get_posts’ callback cannot set the proper query vars. So even if there was not a 404, the page 2 shown would be for the wrong query anyway. The pagination URLs need to contain the same URL parameters as for the first page. This may cure the 404 issue, or not. Even if not, this still must be corrected or you will be getting the wrong content once the 404 issue is fixed.

    Is your template displaying the results using next_posts_link() for pagination? It is supposed to pickup URL parameters from the current request, but even if it fails, you can use the ‘get_pagenum_link’ filter to correct the problem. You can get the missing parameters from the query vars you set or from $_SERVER['REQUEST_URI'].

    Indeed I use the previous_posts_link and the next_posts_link links.
    I think I wasn’t clear about it, but these buttons do take the URL parameters to the next page (my example was wrong).

    I’ve tested it a little bit more and now I it does this (there are 9 posts per page):

    1. With more then 9 posts I use the next_posts_link link and it takes the URL parameter to the next page and that’s working good.
    2. When I’m on page 2 and I click on the “land” with value “Nederland” it filters, because there are 10 “lands” with the value “Nederland”, but it shows on page 2 the 10th post (and it shows the previous_posts_link), so I think that’s why on page 2 with 9 posts or less with a certain value it gives the 404 error.
    3. When I click on a value with for example 2 posts attachted to it (when I’m on page 2) I get the 404 error.

    So, when I’m on the page /cat/activiteit/page/2/ and I start to filter and there are enough posts it’s working, but it shows the last posts and you need to click on the previous link to see the rest. And if there are for example 2 posts it gives the 404 error.

    bcworkz
    Participant

    @bcworkz

    Ah, I see. In a sense, WP is delivering exactly what you ask for, the second page of further filtered results, of which there are none, so WP reports nothing was found, albeit rather rudely. Probably the easiest way to handle this is to always reset the results page to page 1 any time a filter is added.

    The problem is how is WP to know the difference between a new filter request and a next or previous page request? I’ve a couple ideas. One is to add an action attribute to the form tag and fill it with the current request without the URL parameters nor any page element in the permalink. You can get the current request from $_SERVER['REQUEST_URI'].

    The other is to add a hidden field to act as a flag signifying that an additional filter was added. In ‘pre_get_posts’ callback, if this flag/field is found, then set ‘paged’ to 1. The trick here is to remove the URL parameter so that it is not picked up by next or previous links. You should be able to do this by stripping the URL parameter out of the value stored in $_SERVER['REQUEST_URI'].

    Another approach which could be a nice feature if there are several pages of results is to have code try to guess the equivalent page in the filtered results comparable to the current page so that the results on the current page that remain after filtering will still be on the first page shown page after filtering. I’m not quite sure how to do this, perhaps a prorated page number based on the result count of the current page against the filtered count.

    For example, the current page is page 3 of 6 containing 50 results. The filtered result count will be 34 or 4 pages of results, so the first page shown with the filtered results would be page 2 because 2/4 == 3/6.

    I’m not sure that will always work. I suppose the real solution is to identify which of the results on the current page will remain after filtering and determine which filtered page contains the majority of these results.

    I’ll try what you suggested. In the meanwhile I found at that if the permalink settings are default, like /?cat=1 and I’m on page 2 (/?cat=1&paged=2) and I click on a tag the page goes to the page /?tag=example-tag. But when the permalink settings are /%postname%/ then the 404 error occurs.

    The 404 error redirects to the 404 page. When I put the form code in the normal sidebar (sidebar.php) and I remove the tag which causes the error the page redirects back to the results on page 2 (because in the url the link is still /cat/activiteit/page/2/?land=nederland (for example.)

    To let this setup work correctly I need to know how to remove the ?land= and ?provincie= string from the parameter, because then it’s working I think.

    So, the 404 error occurs because of there are not enough search results for page 2.

    I’ll try what you suggested and come back here when I found out more.

Viewing 15 replies - 1 through 15 (of 28 total)
  • The topic ‘[Resolved] Category sidebar seach: checkbox and custom fields problem’ is closed to new replies.