0

I stumbled upon an issue, where I have registered a custom query variable in Wordpress. However, I would like to pass several values with same custom query and return this in an array, but it redirects/only takes into account the last query value.

I have done like so:

function custom_query_vars( $qvars ) {
    $qvars[] = 'filter_fabric';
    return $qvars;
}
add_filter( 'query_vars', 'custom_query_vars' );

However, if I pass the url like:

example.com/category/page/2?filter_fabric=cotton&filter_fabric=polyester

I can see in the $query that only the last parameter, in this case 'polyester' is passed, which results in an error with pagination (when I click on a pagination link with the address example.com/category/page/2?filter_fabric=cotton&filter_fabric=polyester it redirects me to example.com/category/page/2?filter_fabric=polyester

Does anyone know how to solve this issue? Would be of great help!

I have tried adding with this code as well, but it did not work:

function custom_query_vars( $qvars ) {
    $qvars[] .= 'filter_fabric';
    return $qvars;
}
add_filter( 'query_vars', 'custom_query_vars' );

I would assume it has something to do with a foreach loop, which pushes into into $qvars[] as array, but I am not sure how to do that.

EDIT #1

Here is my code for doing the filter with ajax.

In functions.php

function rg_product_filter() {

//unserialize query string in PHP
//parse_str($_POST['value'], $data); We change this to our custom proper_parse_str function in order to get multiple parameters with same name e.g. "/?filter_tygmaterial=sammet&filter_tygmaterial=bomull"
$data = proper_parse_str($_POST['value']);

$product_color = $data['filter_color'];
$product_material = $data['filter_material'];
$product_fabric = $data['filter_fabric'];
$wpquery_vars = $_POST['query_vars'];

//Get current product category id
$product_category = $wpquery_vars['queried_object'];
$product_cat_id = $product_category['term_id'];

//Get current order + orderby if exists, part 2
$args_orderby = $data['filter_orderby'];

//@TODO: Right now it does not work with pagination. 
$per_page = $_POST['per_page'];
$current = $_POST['current'];

$args = array(
    'post_type' => 'product',
    'posts_per_page' => $per_page,
    'paged' => $current,
);
if ($args_orderby) {
    if ($args_orderby === 'price') {
        $args['orderby'] = 'meta_value_num';
        $args['order'] = 'asc';
        $args['meta_key'] = '_price';
    } elseif ($args_orderby === 'price-desc') {
        $args['orderby'] = 'meta_value_num';
        $args['order'] = 'desc';
        $args['meta_key'] = '_price';
    } else {
        $args['orderby'] = $args_orderby;
    }
}
if ($product_category) {
    $args['tax_query'][] = array(
            'taxonomy' => 'product_cat',
            'field' => 'term_id',
            'terms' => $product_cat_id,
    );
}
if ($product_color) {
    $args['tax_query'][] = array(
        'taxonomy' => 'pa_color',
        'field' => 'slug',
        'terms' => $product_color,
    );
}
if ($product_material) {
    $args['tax_query'][] = array(
        'taxonomy' => 'pa_material',
        'field' => 'slug',
        'terms' => $product_material,
    );
}
if ($product_fabric) {
    $args['tax_query'][] = array(
        'taxonomy' => 'pa_tygmaterial',
        'field' => 'slug',
        'terms' => $product_fabric,
    );
}

$loop = new WP_Query( $args );

if ( $loop->have_posts() ) {
    while ( $loop->have_posts() ) : $loop->the_post();
        wc_get_template_part( 'content', 'product' );
    endwhile;
} else {
    do_action( 'woocommerce_no_products_found' );
}

wp_reset_postdata();

wp_die();
}
add_action('wp_ajax_rg_product_filter', 'rg_product_filter');
add_action('wp_ajax_nopriv_rg_product_filter', 'rg_product_filter');

In customjs.js

    //WooCommerce Filters using AJAX
window.addEventListener("load", (event) => {
    
    $filterForm = $('#filter-form') ? $('#filter-form') : '';
    $filterForm.on('submit', function (e) {
        e.preventDefault();

        var values = $(this).serialize();
        var product_container = $('.woocommerce ul.products');

        // console.log('values', values);

        //append browser URL with filters
        let url = window.location.href;
        let filterParams = values.replace(/[^&]+=&/g, '').replace(/&[^&]+=$/g, '') //remove empty values from URL
        let baseUrl = url.includes('?') ? url.split('?')[0] : '',
            paramsInUrl = url.includes('?') ? url.split(('?'))[1] : '',
            paramsInUrlWithoutFilter = paramsInUrl.startsWith('filter') ? '' : paramsInUrl.split('&filter')[0]; //Get all params that are before "filter" if exists any 
        if (filterParams.endsWith('=') && paramsInUrlWithoutFilter) { //Removing from URL when exists other parameters than filter
            filterParams = '';
            url_with_filters = '?' + paramsInUrlWithoutFilter;
            window.history.pushState(null, null, url_with_filters);
        } else if (filterParams.endsWith('=') && !paramsInUrlWithoutFilter) { //Removing from URL when no other parameteres except filter exists
            filterParams = '';
            url_with_filters = location.pathname;
            window.history.pushState(null, null, url_with_filters);
        } else if (paramsInUrlWithoutFilter) { //Appending to browser URL when exists other parameters than filter
            filterParams = filterParams;
            url_with_filters = '?' + paramsInUrlWithoutFilter + '&' + filterParams;
            window.history.pushState(null, null, url_with_filters);
        } else { //Appending to browser URL when no other parameters except filter exists
            filterParams = filterParams;
            url_with_filters = '?' + filterParams;
            window.history.pushState(null, null, url_with_filters);
        }

        //Update product results text
        var resultCountElement = $('.title-wrapper .woocommerce-result-count.rg_woocommerce-total-count');
        
        // Add loader
        product_container.block({
            message: null,
            overlayCSS: {
                cursor: 'none',
                backgroundColor: '#fff',
            },
        });

        $.ajax({
            url: wp_ajax.ajax_url,
            method: 'POST',
            data: {
                value: values,
                action: 'rg_product_filter',
                per_page: wp_ajax.per_page,
                current: wp_ajax.current,
                query_vars: wp_ajax.wp_query_vars,
            },
            success: (res) => {
                // console.log(res);
                $('.woocommerce ul.products').html(res);
                product_container.unblock();

                //Update product results text
                resultCount = $('ul.products li.product').length;
                resultCountElement.html(resultCount + ' produkter');
            },
            error: (res) => {
                console.log(res);
            }

        });

    });

    //Select forms (not e.g. input groups)
    $('select.form-control').each( function () {
        $(this).on('change', $(this), (e) => {
            if (e.handled !== true) { //Adding this if statement, because otherwise code $filterForm.submit(); is fired several times
                e.handled = true;
                $('.wc-filter .sorting__submit').css('display', 'block');
                return;
            }
          });
    });

    //Input groups
    $('.input-group').each( function () {
        $(this).on('change', ':checkbox', (e) => {
            $('.wc-filter .sorting__submit').css('display', 'block');
        });
    });

    // Orderby
    $( '.input-group' ).on( 'change', 'input[type="radio"]', function() {
        $('.wc-filter .sorting__submit').css('display', 'block');
    });
    $('.wc-filter').on('click', '.sorting__submit', function () {
        $filterForm.submit();
        var filterOverlay = document.getElementsByClassName('product-filters-bg-overlay')[0] ? document.getElementsByClassName('product-filters-bg-overlay')[0] : '';
        var filterContainer = document.getElementsByClassName('wc-filter')[0] ? document.getElementsByClassName('wc-filter')[0] : '';
        filterOverlay.classList.toggle('toggled');
        filterContainer.classList.toggle('toggled');
        $('body.woocommerce').css('overflow', '');

    });
    

and lastly, i have filters.php: Ps: Please note that I have changed the name of the input group of fabrics from name=filter_fabric to name=filter_fabric[] as per recommendations

    <?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

$color_terms = get_terms('pa_color');
$material_terms = get_terms('pa_material');
$fabric_terms = get_terms(
    array(
        'taxonomy' => 'pa_tygmaterial',
    ));

?>
<div class="product-filters__panelbody">
    
    <form id="filter-form" method="get" action="rg_product_filter">
        <div class="product-filters-wrapper">
        <?php /* Order By */ ?>
            <div class="product-filters-item catalog-ordering">
                <div class="product-filters-item-heading collapse-button" 
                data-toggle="collapse" 
                data-target="#tab_filter-sorting" 
                aria-expanded="false" 
                aria-controls="#tab_filter-sorting"
                >Sortering
                <svg viewBox="0 0 24 24" class="icon__product-filters-item">
                    <path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
                </svg>
                </div>
                <div class="collapse-body collapse-element collapse"
                    id="tab_filter-sorting"
                    >
                    <div class="product-filters-item-content">
                        <?php echo woocommerce_catalog_ordering(); ?>
                    </div>
                </div>
            </div>
            <?php /* Color */ ?>
            <div class="product-filters-item">
                <div class="product-filters-item-heading collapse-button" 
                data-toggle="collapse" 
                data-target="#tab_filter-color" 
                aria-expanded="false" 
                aria-controls="#tab_filter-color"
                >Color
                <svg viewBox="0 0 24 24" class="icon__product-filters-item">
                    <path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
                </svg>
                </div>
                <div class="collapse-body collapse-element collapse"
                    id="tab_filter-color"
                    >
                    <div class="product-filters-item-content">
                        <select name="filter_color" id="filter_color" class="form-control">
                            <option value="" class="">Color</option>
                            <?php foreach ($color_terms as $color_term) {

                                $selected = $color_term->slug === $_GET['filter_color'] ? 'selected' : '';
                                echo '<option ' . $selected . ' value="' . $color_term->slug . '">' . $color_term->name . '</option>';
                            }?>
                        </select>
                    </div>
                </div>
            </div>
            <?php /* Material */ ?>
            <div class="product-filters-item">
                <div class="product-filters-item-heading collapse-button"
                data-toggle="collapse" 
                data-target="#tab_filter-material" 
                aria-expanded="false" 
                aria-controls="#tab_filter-material"
                >Material
                <svg viewBox="0 0 24 24" class="icon__product-filters-item">
                    <path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
                </svg>
                </div>
                <div class="collapse-body collapse-element collapse"
                    id="tab_filter-material">
                    <div class="product-filters-item-content">
                        <select name="filter_material" id="filter_material" class="form-control">
                            <option value="" class="">Material</option>
                            <?php foreach ($material_terms as $material_term) {

                                $selected = $material_term->slug === $_GET['filter_material'] ? 'selected' : '';
                                echo '<option ' . $selected . ' value="' . $material_term->slug . '">' . $material_term->name . '</option>';
                            }?>
                        </select>
                    </div>
                </div>
            </div>
            <?php /* Fabrics */ ?>
            <div class="product-filters-item">
                <div class="product-filters-item-heading collapse-button"
                data-toggle="collapse" 
                data-target="#tab_filter-fabrics" 
                aria-expanded="false" 
                aria-controls="#tab_filter-fabrics"
                >Fabrics
                <svg viewBox="0 0 24 24" class="icon__product-filters-item">
                    <path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
                </svg>
                </div>
                <div class="collapse-body collapse-element collapse"
                    id="tab_filter-fabrics">
                    <div class="product-filters-item-content">
                        <?php foreach ($fabric_terms as $fabric_term) {

                            //Get parameters from URL
                            $url_parameters = $_SERVER['QUERY_STRING'];
                            $data = proper_parse_str($url_parameters);
                            $product_fabrics = $data['filter_fabric'];

                            //Check if filter input is in array of parameters from URL, and mark the input as "checked"
                            if (isset($product_fabrics)) {

                                if (is_array($product_fabrics)) { //If more than one parameter is checked, $product_tygmaterial becomes array
                                    $checked = in_array($fabric_term->slug, $product_fabrics) ? 'checked' : '';
                                } else { //If only one parameter is checked, $product_tygmaterial becomes string
                                    $checked = $fabric_term->slug === $product_fabrics ? 'checked' : '';
                                }

                            } else {
                                $checked = '';
                            }

                            ?>
                            <div class="input-group">
                                <input type="checkbox" id="<?php echo $fabric_term->slug; ?>" name="filter_fabric[]" value="<?php echo $fabric_term->slug ?>" <?php echo $checked; ?>>
                                <label for="<?php echo $fabric_term->slug; ?>"><?php echo $fabric_term->name; ?></label>
                            </div>
                        <?php }
                    ?>
                    </div>
                </div>
            </div>
        </div>
    </form>
</div>
<?php /* Submit Button for Product Sorting */?>
<div class="submit__button-wrapper">
    <button style="display: none;" class="sorting__submit">Show products</button>
</div>
<?php

Please let me know if this makes sense, I realize it is a lot of code in filters.php but the important part is the part noted with "Fabrics" as that is the input group that I am having trouble with.

Edit #2 I realized there might be an important function in my functions.php file that needs to be mentioned.

This works fine if the parameters are in the "clean/beautiful" state in the URL (i.e. example.com/category/page/2?filter_fabric=cotton&filter_fabric=polyester), but when I am on the paginated link, it automatically redirects to example.com/category/page/2?filter_fabric=polyester. It does this before the page is loaded, as I can see in my dev tools that a 301 request is sent to the second url with only the last parameter. I would assume this is some Wordpress automatic function that does that - is there any way to disable this?

function _url_parameters_query( $query ) {

//Check if query is for archive pages (otherwise this breaks e.g. nav header WP_Query)
if ($query->query_vars['wc_query'] !== 'product_query' || is_admin() ) {
    return;
}

$url_parameters = $_SERVER['QUERY_STRING'];
$data = proper_parse_str($url_parameters);
$product_fabric = $data['filter_fabric'] ? $data['filter_fabric'] : null;


//Check if filter input is in array of parameters from URL, and mark the input as "checked"
if (isset($product_fabric)) {

    // you can't use the query->set here for tax_query as tax query has already been made so you need to need add the data to any existing tax query
    $tax_query = array(
        'taxonomy' => 'pa_tygmaterial',
        'field' => 'slug',
        'terms' => $product_fabric,
        'relation' => 'AND',
    );
    $query->tax_query->queries[] = $tax_query; 
    $query->query_vars['tax_query'] = $query->tax_query->queries;
    if (array_key_exists('filter_fabric', $query->query)) {
        // echo 'hello there Renjo!';
        $query->query['filter_fabric'] = $product_fabric;
    }
}
}
add_action( 'pre_get_posts', '_url_parameters_query' );
  • 1
    not sure what u want but i think you need to change filter_fabric to filter_fabric[] – Amin S Feb 02 '23 at 23:03
  • Hi Amin, Thank you for responding! Yeah, I thought of that too, but the problem is that URL becomes ugly. It becomes like this `example.com/category/?filter_fabric%5B%5D=cotton&filter_fabric%5B%5D=polyester`. Do you know if there is a way to make the URL pretty, if we change to `filter_fabric[]` in the input? – renjogabro Feb 02 '23 at 23:29
  • probably post method – Amin S Feb 02 '23 at 23:30
  • I see, this is actually a filter I created using ajax. If I provide the code, would you be willing to take a look at it? Or do you have any guide about how to do this using ajax? Anything is appreciated! – renjogabro Feb 02 '23 at 23:32
  • yes i can. edit and let me know whenever u changed the post. – Amin S Feb 02 '23 at 23:33
  • Hi again, thank you very much! I have updated the post now, please let me know what you think! Thanks again! – renjogabro Feb 03 '23 at 00:07
  • it looks fine to me ! what 's the prob now ? just somewhere i still found _GET filter_fabric which you should change it to _POST – Amin S Feb 03 '23 at 00:29
  • The problem now is that when I click filter_fabric checkboxes with `name=filter_fabric[]` and submit the form, it prints out: `example.com/category/?filter_fabric=%5B%5Dcotton&filter_fabric%5B%5D=polyester` in the URL. So this is what I would like to avoid – renjogabro Feb 03 '23 at 00:40
  • see this link https://stackoverflow.com/questions/18189187/stop-url-parameters-being-added-on-submit-of-form – Amin S Feb 03 '23 at 00:45
  • Hmm, I read that link, but I cannot get it to work with this particular problem. I think the form is not outputting any special data to URL since we have e.preventDefault in javascript on form submit (I did this to prevent refresh of page when submitting form, as we want to do this through ajax). So the URL is happening through my custom js, but having `name=filter_fabric[]` gives us the bad URL. I am very open to suggestions though! – renjogabro Feb 03 '23 at 01:00
  • i think $filterForm.on('submit', function (e) { here would add something in url – Amin S Feb 03 '23 at 01:14
  • Sorry, I think I might have left out one important bit. I also have a function in `functions.php` that hooks into `pre_get_posts`. So naturally, if the URL had been `example.com/category/page/2?filter_fabric=cotton&filter_fabric=polyester`, this would work without problem, but for some reason when I click such a link, it automatically redirects the page to `example.com/category/page/2?filter_fabric=polyester`. And I believe this is because of some Wordpress function that cleans URL (i would like to disable it). I have made another edit on the post, please check if you have any ideas! Thankyou! – renjogabro Feb 03 '23 at 12:42

1 Answers1

0

I think I resolved this issue by adding the following function in functions.php. I realized it was the redirect that was making problems for me, so I added the following:

//Remove redirection when filters with same name are set in URL (i.e. filters from input_groups), otherwise only passes last value
# e.g. example.com/category/page/2/?filter_fabric=cotton&filter_fabric=polyester => example.com/category/page/2/?filter_fabric=polyester
function _disable_redirect_query_params() {
    global $wp_query;
    if ($wp_query->query_vars['wc_query'] !== 'product_query' || is_admin() ) {
        return;
    }

    if (isset($_GET['filter_tygmaterial']) ) {
       remove_action('template_redirect', 'redirect_canonical');
    }
}
add_action( 'wp', '_disable_redirect_query_params' );

I am not sure if there are any potential side effects of this, so if anyone has something in mind, please comment or let me know!