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!