This website uses cookies to personalize your experience. By using this website you agree to our cookie policy.

Reply To: Creating a results page (not live filtering), sorted

Home Forums Product Support Forums Ajax Search Pro for WordPress Support Creating a results page (not live filtering), sorted Reply To: Creating a results page (not live filtering), sorted

#55260
info_piT4info_piT4
Participant

Ok made some changes, I still need to let my colleague test it, but maybe someone with xstore and asp can use it as a base.
It also has the suggested keywords now. it uses the first suggestion, and searches with it. if there are results it should display them, if not, it should say no results.

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;
});

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 CSS

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; }

As always, test it, change it, improve it. Do not blindly use it haha. Maybe someone can use it as their base to make their own template.