Thanks for the support! My topic “Products per page dropdown gone in mobile view?” has been successfully resolved.
Thanks for the support! My topic “Products per page dropdown gone in mobile view?” has been successfully resolved.
Thank you!
Have nice day and weekend
Ofcourse I use a different attribute for the MPN, but you can customize it to your own meta or attribute field values
No problem, this is what I’ve come with so far:
add_filter( 'woocommerce_product_tabs', function( $tabs ) {
if ( isset( $tabs['description'] ) ) {
$original_callback = $tabs['description']['callback'];
$tabs['description']['callback'] = function( $key, $tab ) use ( $original_callback ) {
global $product;
// Toon eerst de standaard beschrijving
call_user_func( $original_callback, $key, $tab );
// Haal EAN en MPN op
$ean = $product->get_meta( '_global_unique_id' );
$mpn = $product->get_attribute( 'pa_inkoopcode' );
if ( $ean || $mpn ) {
echo '<div class="ean-mpn-extra" style="margin-top:15px;">';
if ( $ean ) {
echo '<p><strong>EAN:</strong> ' . esc_html( $ean ) . '</p>';
} elseif ( $mpn ) {
echo '<p><strong>MPN:</strong> ' . esc_html( $mpn ) . '</p>';
}
echo '</div>';
}
};
}
return $tabs;
});
Hello Andrew,
Thank you for looking into this for me!
I just wanted to know so I know I’m not reinventing the wheel or using something that’s a setting instead 😉
If I made it work I’ll post the snippet here as usual.
Kind regards!
Nancy
Here you go!
I have whitelisted the same IP address as last time, if this is not the correct one, please let me know.
https://www.bakgoed.nl/product/funcakes-mix-voor-biscuit-500-gram/
Mobile view > Tabs > I want to place the EAN and or MPN on the bottom of that tab.
Thanks in advance & Kind regards,
Nancy
Thank you for the link!
Thank you for the suggestion! I will look into it 🙂
Here is the pdf
Thanks for the support! My topic “Mobile view on the single product page, change add to cart and quantity box” has been successfully resolved.
Forgot to update this, this problem is solved, thank you
Sorry for the late response,
I am using ajaxsearchpro (another plugin for this). Since I’ve adapted it to xstore I want to share the process here:
this is what I added to functions.php
// ===== Ajax Search Pro: gedeelde instellingen =====
if ( ! defined('BG_ASP_INSTANCE_ID') ) {
define('BG_ASP_INSTANCE_ID', 8);
}
/*CHANGE THE 8 TO YOUR INSTANCE ID */
/* ---------- Shortcodes renderers ---------- */
add_shortcode('asp_categories', function () {
global $asp_cats;
if (empty($asp_cats) || !is_array($asp_cats)) return '';
ob_start(); ?>
<ul class="asp-cat-list">
<?php foreach ($asp_cats as $c):
$name = isset($c->title) ? $c->title : '';
$link = isset($c->link) ? $c->link : '#';
$thumb = isset($c->image) ? $c->image : '';
?>
<li class="asp-cat-item">
<a href="<?php echo esc_url($link); ?>">
<?php if ($thumb): ?><img src="<?php echo esc_url($thumb); ?>" alt="<?php echo esc_attr($name); ?>"><?php endif; ?>
<span><?php echo esc_html($name); ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
<?php
return ob_get_clean();
});
add_shortcode('asp_products', function () {
global $asp_products;
if (empty($asp_products) || !is_array($asp_products)) return '';
ob_start(); ?>
<div class="asp-products-grid">
<?php foreach ($asp_products as $p):
$title = isset($p->title) ? $p->title : '';
$link = isset($p->link) ? $p->link : '#';
$img = isset($p->image) ? $p->image : '';
$price_html = '';
if (!empty($p->id) && function_exists('wc_get_product')) {
$obj = wc_get_product((int)$p->id);
if ($obj) $price_html = $obj->get_price_html();
}
?>
<a class="asp-product-card" href="<?php echo esc_url($link); ?>">
<?php if ($img): ?><img src="<?php echo esc_url($img); ?>" alt="<?php echo esc_attr($title); ?>"><?php endif; ?>
<h3><?php echo esc_html($title); ?></h3>
<?php if ($price_html): ?><div class="asp-price"><?php echo wp_kses_post($price_html); ?></div><?php endif; ?>
</a>
<?php endforeach; ?>
</div>
<?php
return ob_get_clean();
});
add_shortcode('asp_pages_posts', function () {
global $asp_pages;
if (empty($asp_pages) || !is_array($asp_pages)) return '';
ob_start(); ?>
<ul class="asp-pages-list">
<?php foreach ($asp_pages as $pg):
$title = isset($pg->title) ? $pg->title : '';
$link = isset($pg->link) ? $pg->link : '#';
?>
<li><a href="<?php echo esc_url($link); ?>"><?php echo esc_html($title); ?></a></li>
<?php endforeach; ?>
</ul>
<?php
return ob_get_clean();
});
add_shortcode('asp_other', function () {
global $asp_other;
if (empty($asp_other) || !is_array($asp_other)) return '';
ob_start(); ?>
<ul class="asp-other-list">
<?php foreach ($asp_other as $o):
$title = isset($o->title) ? $o->title : '';
$link = isset($o->link) ? $o->link : '#';
?>
<li><a href="<?php echo esc_url($link); ?>"><?php echo esc_html($title); ?></a></li>
<?php endforeach; ?>
</ul>
<?php
return ob_get_clean();
});
/* ---------- Resultaat-splitsing voor tabs (client-side ASP run) ---------- */
add_filter('asp_results', function($results, $search_id){
if ((int)$search_id !== (int)BG_ASP_INSTANCE_ID) return $results;
if (!wp_doing_ajax()) return $results; // alleen live overlay vullen
if (empty($results)) {
// Niets doen -> niet per ongeluk server-side arrays leegmaken
return $results;
}
global $asp_cats, $asp_products, $asp_pages, $asp_other;
$asp_cats = $asp_products = $asp_pages = $asp_other = [];
foreach ($results as $r) {
$pt = isset($r->post_type) ? $r->post_type : '';
$tx = isset($r->taxonomy) ? $r->taxonomy : '';
if ($tx === 'product_cat' || $pt === 'product_cat') {
$asp_cats[] = $r;
} elseif ($pt === 'product') {
// optioneel: if (empty($r->image)) { ..thumbnail ophalen.. }
$asp_products[] = $r;
} elseif (in_array($pt, ['page','post'], true)) {
$asp_pages[] = $r;
} else {
$asp_other[] = $r;
}
}
return $results;
}, 10, 2);
/* ---------- DEBUG helper (enkelvoudig!) ---------- */
$GLOBALS['bg_asp_debug'] = [];
if ( ! function_exists('bg_dbg') ) {
function bg_dbg($k, $v){ $GLOBALS['bg_asp_debug'][$k] = $v; }
}
/* ---------- (Optioneel) suggested phrases cache voor debugging ---------- */
$GLOBALS['bg_asp_suggested'] = [];
add_filter('asp_suggested_phrases', function($phrases, $search_id){
$phrases = is_array($phrases) ? array_values(array_filter(array_map('trim', $phrases))) : [];
$GLOBALS['bg_asp_suggested'][(int)$search_id] = $phrases;
return $phrases;
}, 10, 2);
/* ---------- Kandidaten generator (NL heuristics) ---------- */
function bg_asp_candidates($orig){
$w = mb_strtolower(trim((string)$orig));
$c = [];
if ($w !== '') $c[] = $w;
// NL heuristics
if (preg_match('~kosten$~u', $w)) {
$c[] = preg_replace('~kosten$~u', 'ing', $w); // bezorgkosten -> bezorging
$c[] = preg_replace('~kosten$~u', 'en', $w); // bezorgkosten -> bezorgen
$c[] = 'verzendkosten';
$c[] = 'verzending';
}
if (strpos($w, 'bezorg') !== false) {
$c[] = 'bezorging';
$c[] = 'bezorgen';
}
// 1e woord als laatste fallback
$parts = preg_split('~\s+~u', $w, -1, PREG_SPLIT_NO_EMPTY);
if (!empty($parts)) $c[] = $parts[0];
// Uniek + schoon
$c = array_values(array_unique(array_filter(array_map('trim', $c))));
return $c;
}
/* ---------- Server-side ASP-run (vult óók tabs) ---------- */
// ===================== Server-side ASP-run (vult óók tabs) =====================
function bg_asp_run_fallback_query($phrase){
$phrase = trim((string)$phrase);
$out_ids = [];
global $asp_cats, $asp_products, $asp_pages, $asp_other;
$asp_cats = is_array($asp_cats) ? $asp_cats : [];
$asp_products = is_array($asp_products) ? $asp_products : [];
$asp_pages = is_array($asp_pages) ? $asp_pages : [];
$asp_other = is_array($asp_other) ? $asp_other : [];
try {
if (class_exists('\\WPDRMS\\ASP\\Query\\SearchQuery')) {
$sq = new \WPDRMS\ASP\Query\SearchQuery([
's' => $phrase,
'_ajax_search' => false,
'posts_per_page' => 50
], (int)BG_ASP_INSTANCE_ID);
$rows = isset($sq->posts) && is_array($sq->posts) ? $sq->posts : [];
foreach ($rows as $res) {
$d = isset($res->asp_data) ? $res->asp_data : (object)[];
$id = isset($d->id) ? (int)$d->id : (isset($res->ID) ? (int)$res->ID : 0);
$ct = isset($d->content_type) ? $d->content_type : '';
$pt = isset($d->post_type) ? $d->post_type : ( $id ? get_post_type($id) : '' );
$tx = isset($d->taxonomy) ? $d->taxonomy : '';
// Termen (categorieën)
if ($ct === 'term' || $tx === 'product_cat' || $pt === 'product_cat') {
$link = ($tx && $id && function_exists('get_term_link')) ? get_term_link($id, $tx) : '#';
if (!is_wp_error($link) && $link) $d->link = $link;
$asp_cats[] = (object)[
'id'=>$id,'title'=>$d->title ?? '','link'=>$d->link ?? '#','image'=>$d->image ?? '',
'post_type'=>$pt,'taxonomy'=>$tx
];
continue;
}
// Producten (robuster): direct product óf variatie → parent
$is_product = ($pt === 'product' || $pt === 'product_variation');
if (!$is_product && $id) {
$gpt = get_post_type($id);
$is_product = ($gpt === 'product' || $gpt === 'product_variation');
$pt = $pt ?: $gpt;
}
if ($is_product) {
// map variatie → parent voor de Woo-loop
if (function_exists('wc_get_product')) {
$p = wc_get_product($id);
if ($p && $p->is_type('variation')) {
$parent_id = $p->get_parent_id();
if ($parent_id) $id = (int)$parent_id;
}
}
$asp_products[] = (object)[
'id'=>$id,'title'=>$d->title ?? ( $id ? get_the_title($id) : '' ),
'link'=>$d->link ?? ( $id ? get_permalink($id) : '#' ),
'image'=>$d->image ?? '','post_type'=>'product','taxonomy'=>''
];
if ($id) $out_ids[] = $id;
continue;
}
// Pagina/bericht
if (in_array($pt, ['page','post'], true)) {
$asp_pages[] = (object)[
'id'=>$id,'title'=>$d->title ?? ( $id ? get_the_title($id) : '' ),
'link'=>$d->link ?? ( $id ? get_permalink($id) : '#' ),
'image'=>$d->image ?? '','post_type'=>$pt,'taxonomy'=>''
];
continue;
}
// Overige
$asp_other[] = (object)[
'id'=>$id,'title'=>$d->title ?? '','link'=>$d->link ?? '#',
'image'=>$d->image ?? '','post_type'=>$pt,'taxonomy'=>$tx
];
}
}
} catch (\Throwable $e) { /* optioneel loggen */ }
// Laat Woo de zichtbaarheid bepalen: filter IDs naar zichtbare + unieke
$out_ids = bg_wc_filter_visible_products($out_ids);
// Eventuele kale WP zoekfallback op producten (optioneel)
if (empty($out_ids)) {
$wpq = new WP_Query([
'post_type' => 'product',
'post_status' => ['publish'],
's' => $phrase,
'posts_per_page' => 50,
'fields' => 'ids'
]);
$out_ids = bg_wc_filter_visible_products( is_array($wpq->posts) ? $wpq->posts : [] );
}
return ['ids' => $out_ids, 'filled' => !empty($out_ids)];
}
function bg_asp_fill_tabs_from_wd_asp(){
if (!function_exists('wd_asp')) return;
$results = (isset(wd_asp()->results) && method_exists(wd_asp()->results, 'getResults'))
? wd_asp()->results->getResults()
: [];
if (empty($results) || !is_array($results)) return;
global $asp_cats, $asp_products, $asp_pages, $asp_other;
$asp_cats = is_array($asp_cats) ? $asp_cats : [];
$asp_products = is_array($asp_products) ? $asp_products : [];
$asp_pages = is_array($asp_pages) ? $asp_pages : [];
$asp_other = is_array($asp_other) ? $asp_other : [];
foreach ($results as $r) {
$obj = (object)[
'id' => isset($r->id) ? (int)$r->id : 0,
'title' => isset($r->title) ? $r->title : '',
'link' => isset($r->link) ? $r->link : '#',
'image' => isset($r->image) ? $r->image : '',
'post_type' => isset($r->post_type) ? $r->post_type : '',
'taxonomy' => isset($r->taxonomy) ? $r->taxonomy : ''
];
if ($obj->taxonomy === 'product_cat' || $obj->post_type === 'product_cat') {
$asp_cats[] = $obj;
} elseif ($obj->post_type === 'product') {
$asp_products[] = $obj;
} elseif (in_array($obj->post_type, ['page','post'], true)) {
$asp_pages[] = $obj;
} else {
$asp_other[] = $obj;
}
}
}
function bg_wc_filter_visible_products(array $ids){
if (!function_exists('wc_get_product')) return array_values(array_unique(array_map('intval',$ids)));
$out = [];
foreach (array_unique(array_map('intval', $ids)) as $id) {
$p = wc_get_product($id);
if (!$p) continue;
// Variatie -> parent
if ($p->is_type('variation')) {
$parent_id = $p->get_parent_id();
if ($parent_id) {
$p = wc_get_product($parent_id);
if (!$p) continue;
$id = $parent_id;
}
}
if (method_exists($p, 'is_visible') ? $p->is_visible() : true) {
$out[] = $id;
}
}
return array_values(array_unique($out));
}
/* ---------- Main hijack van resultatenpagina ---------- */
add_action('pre_get_posts', function($q){
if ( is_admin() || !$q->is_main_query() || !is_search() ) return;
if ((int)($_GET['asp_active'] ?? 0) !== 1 || (int)($_GET['p_asid'] ?? 0) !== (int)BG_ASP_INSTANCE_ID) return;
if (!function_exists('wd_asp')) return;
unset($GLOBALS['bg_asp_predicted']); // reset melding
$orig = isset($_GET['s']) ? trim((string)$_GET['s']) : '';
$q->set('post_type', 'product');
// 1) live ASP results → IDs
$ids = [];
if (isset(wd_asp()->results) && method_exists(wd_asp()->results, 'getResults')) {
foreach ((array) wd_asp()->results->getResults() as $r) {
if (!empty($r->id) && (($r->post_type ?? '') === 'product' || ($r->post_type ?? '') === 'product_variation')) {
$ids[] = (int)$r->id;
}
}
}
$ids = bg_wc_filter_visible_products($ids);
if (!empty($ids)) {
$q->set('post__in', $ids);
$q->set('orderby', 'post__in');
if (function_exists('bg_asp_fill_tabs_from_wd_asp')) bg_asp_fill_tabs_from_wd_asp();
return;
}
// 2) server-side run op ORIGINELE term
$base = bg_asp_run_fallback_query($orig);
if (!empty($base['ids'])) {
$q->set('post__in', $base['ids']);
$q->set('orderby', 'post__in');
return;
}
// 3) kandidaten (alleen als origineel niets oplevert)
foreach (bg_asp_candidates($orig) as $cand) {
if (mb_strtolower($cand) === mb_strtolower($orig)) continue;
$out = bg_asp_run_fallback_query($cand);
if (!empty($out['ids'])) {
$q->set('post__in', $out['ids']);
$q->set('orderby', 'post__in');
$GLOBALS['bg_asp_predicted'] = ['original' => $orig, 'used' => $cand];
return;
}
}
// 4) nog niets
$q->set('post__in', [0]);
}, 20);
/* ---------- Zoek-LIKE uitzetten als we post__in sturen ---------- */
add_filter('posts_search', function($search, $q){
if (
$q->is_main_query()
&& $q->is_search()
&& $q->get('post_type') === 'product'
&& !empty($q->get('post__in'))
) {
return ''; // Laat post__in leidend zijn
}
return $search;
}, 20, 2);
// 1) Sla de keyword suggestions op in een global
add_filter('asp/suggestions/keywords', function($keywords, $phrase){
global $asp_suggestions;
$asp_suggestions = $keywords; // eerste suggestie bewaren
return $keywords;
}, 10, 2);
// 2) Controleer bij een search.php render of er geen resultaten zijn, en trigger fallback
add_action('template_redirect', function() {
if (!is_search()) return;
if (!defined('BG_ASP_INSTANCE_ID')) return;
global $asp_products, $asp_cats, $asp_pages, $asp_other, $asp_suggestions;
// Safeguard arrays
$asp_products = is_array($asp_products) ? $asp_products : [];
$asp_cats = is_array($asp_cats) ? $asp_cats : [];
$asp_pages = is_array($asp_pages) ? $asp_pages : [];
$asp_other = is_array($asp_other) ? $asp_other : [];
// Check: geen resultaten in alle tabs
$total_results = count($asp_products) + count($asp_cats) + count($asp_pages) + count($asp_other);
if ($total_results > 0) return; // resultaten, niks doen
// Check: hebben we een suggestie?
if (empty($asp_suggestions) || !is_array($asp_suggestions)) return;
$first_suggestion = trim($asp_suggestions[0]);
if (!$first_suggestion) return;
// Redirect naar dezelfde zoekpagina met nieuw zoekwoord
$url = add_query_arg('s', urlencode($first_suggestion), home_url('/'));
// Optioneel: forceer ASP active en instance
$url = add_query_arg([
'asp_active' => 1,
'p_asid' => BG_ASP_INSTANCE_ID
], $url);
wp_safe_redirect($url);
exit;
});
I added a search.php:
<?php
/**
* search.php — Tabs met Ajax Search Pro splitsing
* - Producten-tab: gebruikt standaard Woo/XStore archive loop (met wrappers)
* - Andere tabs: eenvoudige weergave via gesplitste ASP-globals
*/
defined('ABSPATH') || exit;
get_header();
if ( ! defined('BG_ASP_INSTANCE_ID') ) define('BG_ASP_INSTANCE_ID', 8);
global $asp_cats, $asp_products, $asp_pages, $asp_other;
// Query markers
$search_query = get_search_query();
$p_asid = isset($_GET['p_asid']) ? (int) $_GET['p_asid'] : 0;
$asp_active = isset($_GET['asp_active']) ? (int) $_GET['asp_active'] : 0;
$use_asp = ($asp_active === 1 && $p_asid === (int)BG_ASP_INSTANCE_ID);
// Safeguards
$asp_cats = is_array($asp_cats) ? $asp_cats : [];
$asp_products = is_array($asp_products) ? $asp_products : [];
$asp_pages = is_array($asp_pages) ? $asp_pages : [];
$asp_other = is_array($asp_other) ? $asp_other : [];
// Bepaal welke tabs zichtbaar zijn
global $wp_query;
$has_wc_products = $use_asp && isset($wp_query) && (int)$wp_query->post_count > 0;
$show_products = $has_wc_products;
$show_cats = !empty($asp_cats);
$show_pages = !empty($asp_pages);
$show_other = !empty($asp_other);
// Eerste actieve tab
$active_tab = $show_products ? 'products' : ($show_cats ? 'cats' : ($show_pages ? 'pages' : ($show_other ? 'other' : 'none')));
?>
<div class="custom-search-tabs-container">
<div class="container">
<div class="search-header">
<h1 class="search-title">
<?php echo $search_query
? sprintf( esc_html__('Zoekresultaten voor: “%s”', 'xstore-child'), esc_html($search_query) )
: esc_html__('Zoekresultaten', 'xstore-child'); ?>
</h1>
<div class="search-form-wrapper">
<?php echo do_shortcode('[wpdreams_ajaxsearchpro id='.intval(BG_ASP_INSTANCE_ID).']'); ?>
</div>
</div>
<?php if ( isset($_GET['debug']) && current_user_can('manage_options') ) : ?>
<pre style="white-space:pre-wrap;background:#111;color:#eee;padding:12px;margin:12px 0;max-height:60vh;overflow:auto;">
<?php
global $wp_query, $asp_cats, $asp_products, $asp_pages, $asp_other;
$dump = [
'GET' => $_GET,
'BG_ASP_INSTANCE_ID' => defined('BG_ASP_INSTANCE_ID') ? BG_ASP_INSTANCE_ID : null,
'use_asp' => isset($use_asp) ? $use_asp : null,
'wp_query->post_count' => isset($wp_query) ? (int)$wp_query->post_count : null,
'wp_query->found_posts' => isset($wp_query) ? (int)$wp_query->found_posts : null,
'asp_products_count' => is_array($asp_products) ? count($asp_products) : null,
'asp_cats_count' => is_array($asp_cats) ? count($asp_cats) : null,
'asp_pages_count' => is_array($asp_pages) ? count($asp_pages) : null,
'asp_other_count' => is_array($asp_other) ? count($asp_other) : null,
'bg_asp_predicted' => $GLOBALS['bg_asp_predicted'] ?? null,
'bg_asp_suggested' => $GLOBALS['bg_asp_suggested'] ?? null,
'bg_asp_debug' => $GLOBALS['bg_asp_debug'] ?? null,
'bg_asp_force_ids' => $GLOBALS['bg_asp_force_ids'] ?? null,
];
print_r($dump);
?>
</pre>
<?php endif; ?>
<?php
if ( ! empty($GLOBALS['bg_asp_predicted']) ) : ?>
<p class="asp-predicted-note">
<?php printf( esc_html__('Geen exacte matches voor “%1$s”. We tonen resultaten voor “%2$s”.', 'xstore-child'),
esc_html($GLOBALS['bg_asp_predicted']['original']),
esc_html($GLOBALS['bg_asp_predicted']['used'])
); ?>
</p>
<?php endif; ?>
<?php if (!$use_asp): ?>
<p class="asp-inactive-note"><?php esc_html_e('Ajax Search Pro is niet actief voor deze aanvraag (asp_active=1 & p_asid ontbreken).', 'xstore-child'); ?></p>
<?php get_search_form(); ?>
<?php else: ?>
<div class="custom-tabs">
<ul class="tab-titles" role="tablist">
<?php if ($show_products): ?>
<li class="tab-title <?php echo ($active_tab==='products'?'active':''); ?>" data-tab="tab-products" role="tab"><?php esc_html_e('Producten','xstore-child'); ?></li>
<?php endif; ?>
<?php if ($show_cats): ?>
<li class="tab-title <?php echo ($active_tab==='cats'?'active':''); ?>" data-tab="tab-cats" role="tab"><?php esc_html_e('Categorieën','xstore-child'); ?></li>
<?php endif; ?>
<?php if ($show_pages): ?>
<li class="tab-title <?php echo ($active_tab==='pages'?'active':''); ?>" data-tab="tab-pages" role="tab"><?php esc_html_e('Pagina’s','xstore-child'); ?></li>
<?php endif; ?>
<?php if ($show_other): ?>
<li class="tab-title <?php echo ($active_tab==='other'?'active':''); ?>" data-tab="tab-other" role="tab"><?php esc_html_e('Overige','xstore-child'); ?></li>
<?php endif; ?>
</ul>
<div class="tab-panels">
<?php if ($show_products): ?>
<div id="tab-products" class="tab-pane <?php echo ($active_tab==='products'?'active':''); ?>">
<?php
// === Render producten o.b.v. ASP-IDs, met Woo pagination ===
// 1) Verzamel ASP product IDs in ASP-volgorde
$asp_ids = [];
foreach ($asp_products as $r) {
if (!empty($r->id)) $asp_ids[] = (int) $r->id;
}
// 2) Als er niets is, toon nette no-products
if (empty($asp_ids)) {
do_action('woocommerce_before_main_content');
do_action('woocommerce_no_products_found');
do_action('woocommerce_after_main_content');
} else {
// 3) Paginering en query
$paged = max(1, get_query_var('paged') ? (int) get_query_var('paged') : (isset($_GET['paged']) ? (int) $_GET['paged'] : 1));
$per_page = (int) apply_filters('loop_shop_per_page', 12); // sluit aan op Woo instelling/filter
$args = [
'post_type' => 'product',
'post__in' => $asp_ids,
'orderby' => 'post__in',
'posts_per_page' => $per_page,
'paged' => $paged,
];
$q = new WP_Query($args);
// 4) Woo loop-context en render
do_action('woocommerce_before_main_content');
// Zorg dat Woo weet dat dit een "search-like" loop is
if (function_exists('wc_set_loop_prop')) {
wc_set_loop_prop('is_search', true);
wc_set_loop_prop('current_page', $paged);
wc_set_loop_prop('per_page', $per_page);
wc_set_loop_prop('total', (int) $q->found_posts);
}
if ($q->have_posts()) {
// Tijdelijk de global $wp_query wisselen zodat Woo pagination en hooks goed werken
global $wp_query;
$old_wp_query = $wp_query;
$wp_query = $q;
do_action('woocommerce_before_shop_loop');
woocommerce_product_loop_start();
while ($q->have_posts()) {
$q->the_post();
if (get_post_type() !== 'product') continue;
wc_get_template_part('content', 'product');
}
woocommerce_product_loop_end();
do_action('woocommerce_after_shop_loop'); // pagination etc.
// Herstel globals
wp_reset_postdata();
$wp_query = $old_wp_query;
} else {
do_action('woocommerce_no_products_found');
}
do_action('woocommerce_after_main_content');
}
?>
</div>
<?php endif; ?>
<?php if ($show_cats): ?>
<div id="tab-cats" class="tab-pane <?php echo ($active_tab==='cats'?'active':''); ?>">
<ul class="asp-cat-list">
<?php foreach ($asp_cats as $c):
$name = isset($c->title) ? $c->title : '';
$link = isset($c->link) ? $c->link : '#';
$thumb = isset($c->image) ? $c->image : '';
?>
<li class="asp-cat-item">
<a href="<?php echo esc_url($link); ?>">
<?php if ($thumb): ?><img src="<?php echo esc_url($thumb); ?>" alt="<?php echo esc_attr($name); ?>"><?php endif; ?>
<span><?php echo esc_html($name); ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if ($show_pages): ?>
<div id="tab-pages" class="tab-pane <?php echo ($active_tab==='pages'?'active':''); ?>">
<ul class="asp-pages-list">
<?php foreach ($asp_pages as $pg): ?>
<li><a href="<?php echo esc_url(isset($pg->link)?$pg->link:'#'); ?>"><?php echo esc_html(isset($pg->title)?$pg->title:''); ?></a></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if ($show_other): ?>
<div id="tab-other" class="tab-pane <?php echo ($active_tab==='other'?'active':''); ?>">
<ul class="asp-other-list">
<?php foreach ($asp_other as $o): ?>
<li><a href="<?php echo esc_url(isset($o->link)?$o->link:'#'); ?>"><?php echo esc_html(isset($o->title)?$o->title:''); ?></a></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php
// Klein tabs-script inline
add_action('wp_footer', function(){ ?>
<script>
(function(){
var wrap = document.querySelector('.custom-tabs');
if(!wrap) return;
var titles = wrap.querySelectorAll('.tab-title');
var panes = wrap.querySelectorAll('.tab-pane');
function activate(id){
titles.forEach(function(t){ t.classList.toggle('active', t.getAttribute('data-tab')===id); });
panes.forEach(function(p){ p.classList.toggle('active', p.id===id); });
}
titles.forEach(function(t){
t.addEventListener('click', function(e){
e.preventDefault();
activate(this.getAttribute('data-tab'));
});
});
})();
</script>
<?php }, 5);
get_footer();
And this is the CSS which I added through the xstore > css settings in the frontend
body.search .sidebar-enabled.sidebar-left {
display: none !important;
}
/* Maak content breder als sidebar weg is */
body.search .container.sidebar-mobile-off_canvas.content-page {
max-width: 100% !important;
padding-left: 15px;
padding-right: 15px;
}
body.custom-search-page .col-md-9.col-md-push-3 {
width: 100% !important;
float: none !important;
}
body.is-search-page .col-md-3.col-md-pull-9.sidebar-enabled.sidebar.sidebar-left {
display: none !important;
}
.custom-tabs { margin-top: 1.5rem; }
.custom-tabs .tab-titles { display:flex; gap:1rem; list-style:none; padding:0; margin:0 0 1rem; border-bottom:1px solid rgba(0,0,0,.08); }
.custom-tabs .tab-title { padding:.5rem 0; cursor:pointer; position:relative; color:inherit; opacity:.7; }
.custom-tabs .tab-title.active { opacity:1; }
.custom-tabs .tab-title.active::after {
content:""; position:absolute; left:0; right:0; bottom:-1px; height:2px; background: currentColor;
}
.custom-tabs .tab-panels .tab-pane { display:none; }
.custom-tabs .tab-panels .tab-pane.active { display:block; }
.asp-cat-list, .asp-pages-list, .asp-other-list { list-style:none; padding:0; margin:0; }
.asp-cat-item { display:flex; align-items:center; gap:.5rem; margin:0 0 12px; }
.asp-cat-item img { width:40px; height:40px; object-fit:cover; border-radius:4px; }
This is also a warning for people who are using searchanise; we were using it for years and years (think about 8 years).
They have blocked us from using the admin and deleted the package we were using. And forcing us to use a new package that would triple the costs.
They did that without any warning, so beware if you use them.
I think he wants the direction of the text vertically (look at his post at July 24, 2025 at 13:36)
Maybe add:
.etheme-elementor-off-canvas__toggle .elementor-button-icon .et-filter:before {
content: "\e95d Filter";
writing-mode: vertical-rl; /* or vertical-lr */
text-orientation: upright; /* optional, adjusts the orientation of the characters */
}
?
Thanks for the support! My topic “Product filter suddenly only showing 3 options” has been successfully resolved.
Thank you for your help!
Are you maybe looking for something like this?
https://www.8theme.com/topic/replace-mobile-filter-icon-with-filter-text/
Thanks in advance!
Whitelisted, and disabled 2fa
I need to whitelist your IP address for the FTP access, can I have your IP?
Here is an example URL
https://www.bakgoed.nl/product-categorie/categorieen/bakmixen-ijsmixen/
Thank you that works!
Well, I think I once made adjustments from another post in the past, for wp-rocket settings for xstore. But that could also be google speed optimization – recommendations
See the private content area for details
is it possible to give an ip address so I can whitelist you for access?
Ofcourse I understand. Maybe you just knew the trick to make it work 😉
Well for now this is my third version. It works with the other filters, but only if the other filters are chosen first, and the dimension is chosen last. If you guys have any ideas on how to fix it so the order doesn’t matter, let me know
class Custom_Dimensions_Filter_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'custom_dimensions_filter',
esc_html__('Custom Dimensions Filter', 'text_domain'),
array('description' => esc_html__('A widget to filter products by custom dimensions', 'text_domain'))
);
}
public function widget($args, $instance) {
$title = apply_filters('widget_title', $instance['title']);
if (is_shop() || is_product_category()) {
$current_category = get_queried_object();
// Check if there are products with dimensions in the current category
if ($this->category_has_dimensions($current_category->term_id)) {
echo $args['before_widget'];
if (!empty($title)) {
echo $args['before_title'] . $title . $args['after_title'];
}
// Capture existing filter parameters
$existing_filters = $_GET;
// Generate filter links with existing parameters
$current_url = $this->get_current_page_url();
$width_options = $this->get_dimension_options('width', $current_category->term_id);
$length_options = $this->get_dimension_options('length', $current_category->term_id);
?>
<form id="custom-filters" method="get">
<?php
foreach ($existing_filters as $key => $value) {
if (strpos($key, 'filter_') === 0) {
echo "<input type='hidden' name='$key' value='" . esc_attr($value) . "' />";
}
}
?>
<select name="filter_width" id="filter_width">
<option value=""><?php _e('Select width in cm', 'text_domain'); ?></option>
<?php echo $width_options; ?>
</select>
<select name="filter_length" id="filter_length">
<option value=""><?php _e('Select length in cm', 'text_domain'); ?></option>
<?php echo $length_options; ?>
</select>
</form>
<script>
jQuery(function($) {
$('#filter_width, #filter_length').change(function() {
$('#custom-filters').submit();
});
});
</script>
<?php
echo $args['after_widget'];
}
}
}
public function form($instance) {
$title = !empty($instance['title']) ? $instance['title'] : esc_html__('Filter by Dimensions', 'text_domain');
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_attr_e('Title:', 'text_domain'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>"
name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text"
value="<?php echo esc_attr($title); ?>">
</p>
<?php
}
private function get_current_page_url() {
global $wp;
return home_url(add_query_arg(array(), $wp->request));
}
private function get_dimension_options($dimension, $category_id) {
$ranges = array('1-10', '11-20', '21-30', '31-40', '41-50'); // Adjusted ranges as per your requirement
$options = '';
foreach ($ranges as $range) {
$selected_count = $this->count_products_with_filters($dimension, $range, $category_id);
if ($selected_count > 0) {
$selected = isset($_GET["filter_$dimension"]) && $_GET["filter_$dimension"] == $range ? 'selected' : '';
$options .= '<option value="' . esc_attr($range) . '" ' . $selected . '>' . esc_html($range) . ' (' . $selected_count . ')</option>';
}
}
return $options;
}
private function count_products_with_filters($dimension, $range, $category_id) {
global $wpdb;
$meta_key = '_' . $dimension;
$meta_value = explode('-', $range);
// Prepare meta query
$meta_query = array(
'relation' => 'AND',
array(
'key' => $meta_key,
'value' => floatval($meta_value[0]),
'compare' => '>=',
'type' => 'DECIMAL(10,2)',
),
array(
'key' => $meta_key,
'value' => floatval($meta_value[1]),
'compare' => '<=',
'type' => 'DECIMAL(10,2)',
)
);
// Add taxonomy query
$tax_query = array(
array(
'taxonomy' => 'product_cat',
'field' => 'term_id',
'terms' => $category_id
)
);
// Add color filter if selected
if (isset($_GET['filter_kleur']) && !empty($_GET['filter_kleur'])) {
$tax_query[] = array(
'taxonomy' => 'pa_kleur', // Replace with your taxonomy name
'field' => 'slug',
'terms' => sanitize_text_field($_GET['filter_kleur']),
'operator' => 'IN'
);
}
// Count matching products
$query = new WP_Query(array(
'post_type' => 'product',
'posts_per_page' => -1, // Count all posts
'meta_query' => $meta_query,
'tax_query' => $tax_query,
));
return $query->found_posts;
}
private function category_has_dimensions($category_id) {
global $wpdb;
$meta_query = array('relation' => 'OR');
$meta_keys = array('_width', '_length');
foreach ($meta_keys as $meta_key) {
$meta_query[] = array(
'key' => $meta_key,
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC'
);
}
$query = new WP_Query(array(
'post_type' => 'product',
'posts_per_page' => 1,
'meta_query' => $meta_query,
'tax_query' => array(
array(
'taxonomy' => 'product_cat',
'field' => 'term_id',
'terms' => $category_id
)
)
));
return $query->have_posts();
}
}
// Register the widget
function register_custom_dimensions_filter_widget() {
register_widget('Custom_Dimensions_Filter_Widget');
}
add_action('widgets_init', 'register_custom_dimensions_filter_widget');
// Add custom filter query logic
function custom_filter_query($query) {
if (!is_admin() && $query->is_main_query() && (is_shop() || is_product_category())) {
$meta_query = array('relation' => 'AND');
$filtering = false;
// Handle width filter
if (isset($_GET['filter_width']) && !empty($_GET['filter_width'])) {
$width_range = explode('-', sanitize_text_field($_GET['filter_width']));
if (count($width_range) == 2) {
$meta_query[] = array(
'key' => '_width',
'value' => array(floatval($width_range[0]), floatval($width_range[1])),
'compare' => 'BETWEEN',
'type' => 'DECIMAL(10,2)',
);
$filtering = true;
}
}
// Handle length filter
if (isset($_GET['filter_length']) && !empty($_GET['filter_length'])) {
$length_range = explode('-', sanitize_text_field($_GET['filter_length']));
if (count($length_range) == 2) {
$meta_query[] = array(
'key' => '_length',
'value' => array(floatval($length_range[0]), floatval($length_range[1])),
'compare' => 'BETWEEN',
'type' => 'DECIMAL(10,2)',
);
$filtering = true;
}
}
// Add more custom filters here as needed
// Only set the meta query if a filter is being applied
if ($filtering) {
$query->set('meta_query', $meta_query);
}
}
}
add_action('pre_get_posts', 'custom_filter_query');
Thank you this works!
Thank you for your adjustments!
I placed the code on pastebin and made a topic on the feature request.
I also updated the code, excluding the default category from the breadcrumbs (often called uncategorized).
Yes it works, thank you for your amazing support as always, and have a nice day