Home > database >  Adding multiple values to a query variable in WordPress
Adding multiple values to a query variable in WordPress

Time:02-05

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 >
    
    <form id="filter-form" method="get" action="rg_product_filter">
        <div >
        <?php /* Order By */ ?>
            <div >
                <div  
                data-toggle="collapse" 
                data-target="#tab_filter-sorting" 
                aria-expanded="false" 
                aria-controls="#tab_filter-sorting"
                >Sortering
                <svg viewBox="0 0 24 24" >
                    <path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
                </svg>
                </div>
                <div 
                    id="tab_filter-sorting"
                    >
                    <div >
                        <?php echo woocommerce_catalog_ordering(); ?>
                    </div>
                </div>
            </div>
            <?php /* Color */ ?>
            <div >
                <div  
                data-toggle="collapse" 
                data-target="#tab_filter-color" 
                aria-expanded="false" 
                aria-controls="#tab_filter-color"
                >Color
                <svg viewBox="0 0 24 24" >
                    <path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
                </svg>
                </div>
                <div 
                    id="tab_filter-color"
                    >
                    <div >
                        <select name="filter_color" id="filter_color" >
                            <option value="" >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 >
                <div 
                data-toggle="collapse" 
                data-target="#tab_filter-material" 
                aria-expanded="false" 
                aria-controls="#tab_filter-material"
                >Material
                <svg viewBox="0 0 24 24" >
                    <path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
                </svg>
                </div>
                <div 
                    id="tab_filter-material">
                    <div >
                        <select name="filter_material" id="filter_material" >
                            <option value="" >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 >
                <div 
                data-toggle="collapse" 
                data-target="#tab_filter-fabrics" 
                aria-expanded="false" 
                aria-controls="#tab_filter-fabrics"
                >Fabrics
                <svg viewBox="0 0 24 24" >
                    <path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
                </svg>
                </div>
                <div 
                    id="tab_filter-fabrics">
                    <div >
                        <?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 >
                                <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 >
    <button style="display: none;" >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' );

CodePudding user response:

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!

  • Related