MOON
Server: Apache
System: Linux server.royaltuning.hu 4.18.0-425.13.1.el8_7.x86_64 #1 SMP Tue Feb 21 04:20:52 EST 2023 x86_64
User: royaltuning (1001)
PHP: 8.2.30
Disabled: exec,passthru,shell_exec,system
Upload Files
File: /home/royaltuning/public_html/public/wp-content/themes/hello-elementor-child/functions.php
<?php
/*
 * This is the child theme for Hello Elementor theme, generated with Generate Child Theme plugin by catchthemes.
 *
 * (Please see https://developer.wordpress.org/themes/advanced-topics/child-themes/#how-to-create-a-child-theme)
 */
add_action( 'wp_enqueue_scripts', 'hello_elementor_child_enqueue_styles' );
function hello_elementor_child_enqueue_styles() {
    wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
    wp_enqueue_style( 'child-style',
        get_stylesheet_directory_uri() . '/style.css',
        array('parent-style')
    );
}
/*
 * Your code goes below
 */
function my_custom_js()
{
    echo '<!-- TikTok Pixel Code Start -->
<script>
!function (w, d, t) {
  w.TiktokAnalyticsObject=t;var ttq=w[t]=w[t]||[];ttq.methods=["page","track","identify","instances","debug","on","off","once","ready","alias","group","enableCookie","disableCookie","holdConsent","revokeConsent","grantConsent"],ttq.setAndDefer=function(t,e){t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}};for(var i=0;i<ttq.methods.length;i++)ttq.setAndDefer(ttq,ttq.methods[i]);ttq.instance=function(t){for(
var e=ttq._i[t]||[],n=0;n<ttq.methods.length;n++)ttq.setAndDefer(e,ttq.methods[n]);return e},ttq.load=function(e,n){var r="https://analytics.tiktok.com/i18n/pixel/events.js",o=n&&n.partner;ttq._i=ttq._i||{},ttq._i[e]=[],ttq._i[e]._u=r,ttq._t=ttq._t||{},ttq._t[e]=+new Date,ttq._o=ttq._o||{},ttq._o[e]=n||{};n=document.createElement("script")
;n.type="text/javascript",n.async=!0,n.src=r+"?sdkid="+e+"&lib="+t;e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(n,e)};


  ttq.load("CSDOD5BC77U6ERKKAFKG");
  ttq.page();
}(window, document, "ttq");
</script>
<!-- TikTok Pixel Code End -->';
}

// Add hook for front-end <head></head>
add_action('wp_head', 'my_custom_js');

// Set billing address fields to not required
add_filter('woocommerce_checkout_fields', 'unrequire_checkout_fields');

function unrequire_checkout_fields($fields)
{
    $fields['billing']['billing_company']['required'] = false;
    $fields['billing']['billing_city']['required'] = false;
    $fields['billing']['billing_postcode']['required'] = false;
    $fields['billing']['billing_country']['required'] = false;
    $fields['billing']['billing_state']['required'] = false;
    $fields['billing']['billing_address_1']['required'] = false;
    $fields['billing']['billing_address_2']['required'] = false;
    $fields['billing']['billing_phone']['required'] = true;

    unset($fields['billing']['billing_address_2']);


    return $fields;
}

add_action('wp_footer', 'my_footer_scripts');
function my_footer_scripts()
{
    ?>
    <!-- ÁRUKERESŐ.HU - PLEASE DO NOT MODIFY THE LINES BELOW -->
    <script type="text/javascript">
        if (ak_widget_params === undefined || ak_widget_script === undefined) {
            var ak_widget_params = ["a10f91ce3e92220ea7a29817cd3c58b0", "R", "HU", 0, "W", 0, 480];
            var ak_widget_script = document.createElement("script");
            ak_widget_script.type = "text/javascript";
            ak_widget_script.src = "https://static.arukereso.hu/widget/presenter.js";
            ak_widget_script.async = true;
            document.body.appendChild(ak_widget_script);
        }
    </script>
    <!-- ÁRUKERESŐ.HU CODE END -->

    <script id="barat_hud_sr_script">var hst = document.createElement("script");
        hst.src = "//admin.fogyasztobarat.hu/h-api.js";
        hst.type = "text/javascript";
        hst.setAttribute("data-id", "9JUBDRAA");
        hst.setAttribute("id", "fbarat");
        var hs = document.getElementById("barat_hud_sr_script");
        hs.parentNode.insertBefore(hst, hs);</script>
    <?php
}

function load_my_script()
{
    wp_register_script(
        'my_script',
        get_stylesheet_directory_uri()  . '/js/custom.js',
        array('jquery')
    );
    wp_enqueue_script('my_script');
}

add_action('wp_enqueue_scripts', 'load_my_script');

function disable_wc_terms_toggle()
{
    remove_action("woocommerce_checkout_terms_and_conditions", "wc_terms_and_conditions_page_content", 30);
}

add_action("wp", "disable_wc_terms_toggle");

add_filter('woocommerce_product_tabs', 'my_shipping_tab');

function my_shipping_tab($tabs)
{
    // Adds the new tab
    $tabs[] = array(
        'title' => __('Szállítási & Fizetési információk', 'child-theme'),
        'priority' => 50,
        'callback' => 'my_shipping_tab_callback'
    );
    return $tabs;
}

add_action( 'comment_form_after', 'ecommercehints_comment_form_before_fields', 10 );
function ecommercehints_comment_form_before_fields() {
    if (is_product()) {
        echo "<script defer async src='https://cdn.trustindex.io/loader.js?2d402ea3187026089246be1e473'></script>";

    }
}

function my_shipping_tab_callback()
{

    // The new tab content
    echo '<h2>Szállítási & Fizetési információk</h2>';
    echo '<div class="elementor-widget-wrap elementor-element-populated">
								<div class="elementor-element elementor-element-3dee91f elementor-widget elementor-widget-heading" data-id="3dee91f" data-element_type="widget" data-widget_type="heading.default">
				<div class="elementor-widget-container">
			<style>/*! elementor - v3.6.5 - 27-04-2022 */
.elementor-heading-title{padding:0;margin:0;line-height:1}.elementor-widget-heading .elementor-heading-title[class*=elementor-size-]>a{color:inherit;font-size:inherit;line-height:inherit}.elementor-widget-heading .elementor-heading-title.elementor-size-small{font-size:15px}.elementor-widget-heading .elementor-heading-title.elementor-size-medium{font-size:19px}.elementor-widget-heading .elementor-heading-title.elementor-size-large{font-size:29px}.elementor-widget-heading .elementor-heading-title.elementor-size-xl{font-size:39px}.elementor-widget-heading .elementor-heading-title.elementor-size-xxl{font-size:59px}</style><h2 class="elementor-heading-title elementor-size-default">Szállítással kapcsolatos kérdések</h2>		</div>
				</div>
				<div class="elementor-element elementor-element-4af542cc elementor-widget elementor-widget-accordion" data-id="4af542cc" data-element_type="widget" data-widget_type="accordion.default">
				<div class="elementor-widget-container">
			<style>/*! elementor - v3.6.5 - 27-04-2022 */
.elementor-accordion{text-align:left}.elementor-accordion .elementor-accordion-item{border:1px solid #d4d4d4}.elementor-accordion .elementor-accordion-item+.elementor-accordion-item{border-top:none}.elementor-accordion .elementor-tab-title{margin:0;padding:15px 20px;font-weight:700;line-height:1;cursor:pointer;outline:none}.elementor-accordion .elementor-tab-title .elementor-accordion-icon{display:inline-block;width:1.5em}.elementor-accordion .elementor-tab-title .elementor-accordion-icon svg{width:1em;height:1em}.elementor-accordion .elementor-tab-title .elementor-accordion-icon.elementor-accordion-icon-right{float:right;text-align:right}.elementor-accordion .elementor-tab-title .elementor-accordion-icon.elementor-accordion-icon-left{float:left;text-align:left}.elementor-accordion .elementor-tab-title .elementor-accordion-icon .elementor-accordion-icon-closed{display:block}.elementor-accordion .elementor-tab-title .elementor-accordion-icon .elementor-accordion-icon-opened,.elementor-accordion .elementor-tab-title.elementor-active .elementor-accordion-icon-closed{display:none}.elementor-accordion .elementor-tab-title.elementor-active .elementor-accordion-icon-opened{display:block}.elementor-accordion .elementor-tab-content{display:none;padding:15px 20px;border-top:1px solid #d4d4d4}@media (max-width:767px){.elementor-accordion .elementor-tab-title{padding:12px 15px}.elementor-accordion .elementor-tab-title .elementor-accordion-icon{width:1.2em}.elementor-accordion .elementor-tab-content{padding:7px 15px}}</style>		<div class="elementor-accordion" role="tablist">
							<div class="elementor-accordion-item">
					<div id="elementor-tab-title-1251" class="elementor-tab-title elementor-active" data-tab="1" role="tab" aria-controls="elementor-tab-content-1251" aria-expanded="true" tabindex="0" aria-selected="true">
													<span class="elementor-accordion-icon elementor-accordion-icon-left" aria-hidden="true">
															<span class="elementor-accordion-icon-closed"><i class="fas fa-plus"></i></span>
								<span class="elementor-accordion-icon-opened"><i class="fas fa-minus"></i></span>
														</span>
												<a class="elementor-accordion-title" href="">Mennyi a szállítási idő, Szállitási költség?</a>
					</div>
					<div id="elementor-tab-content-1251" class="elementor-tab-content elementor-clearfix elementor-active" data-tab="1" role="tabpanel" aria-labelledby="elementor-tab-title-1251" style="display: block;"><p>A szállítási idő <strong>1-2 munkanap</strong>, GLS, Foxpost és Packeta esetében is. A 14:00ig beérkezett rendeléseket még aznap feladjuk. Így az esetek 99%-ban már másnapra kiérkeznek.</p><p><strong>Szállítási költségek: <br></strong>
					-GLS házhozszállítás és csomagpont: <em>1499 Ft</em><strong><br></strong>
					-Foxpost automata:<strong>: </strong><em>1190 Ft</em><strong><br></strong>
					-Packeta csomagpont: <em>1190 Ft</em><br></strong>
					-MPL házhozszállítás és csomagpont: <em>1499 Ft</em></p><p>
					30.000 ft felett ingyenes a szállítás</p></div>
			
				</div>
							<div class="elementor-accordion-item">
					<div id="elementor-tab-title-1252" class="elementor-tab-title" data-tab="2" role="tab" aria-controls="elementor-tab-content-1252" aria-expanded="false">
													<span class="elementor-accordion-icon elementor-accordion-icon-left" aria-hidden="true">
															<span class="elementor-accordion-icon-closed"><i class="fas fa-plus"></i></span>
								<span class="elementor-accordion-icon-opened"><i class="fas fa-minus"></i></span>
														</span>
												<a class="elementor-accordion-title" href="">Raktáron vannak-e a termékek vagy rendelni kell?</a>
					</div>
					<div id="elementor-tab-content-1252" class="elementor-tab-content elementor-clearfix" data-tab="2" role="tabpanel" aria-labelledby="elementor-tab-title-1252"><p>Mindegyik terméknél jelzi automatikusan a rendszer, hogy <strong>készleten van-e</strong> vagy <strong>előrendelhető</strong>. Ha előrendelhető akkor is adható le rendelés, csak várni kell a beszerzésére. Átlagosan 5-7 nap alatt érkeznek meg az “előrendelhető” termékek.</p></div>
				</div>
								</div>
				</div>
				</div>
				<div class="elementor-element elementor-element-4980b68f elementor-widget elementor-widget-heading" data-id="4980b68f" data-element_type="widget" data-widget_type="heading.default">
				<div class="elementor-widget-container">
			<h2 class="elementor-heading-title elementor-size-default">Megrendelésse kapcsolatos kérdések</h2>		</div>
				</div>
				<div class="elementor-element elementor-element-46be81be elementor-widget elementor-widget-accordion" data-id="46be81be" data-element_type="widget" data-widget_type="accordion.default">
				<div class="elementor-widget-container">
					<div class="elementor-accordion" role="tablist">
							<div class="elementor-accordion-item">
					<div id="elementor-tab-title-1181" class="elementor-tab-title elementor-active" data-tab="1" role="tab" aria-controls="elementor-tab-content-1181" aria-expanded="true" tabindex="0" aria-selected="true">
													<span class="elementor-accordion-icon elementor-accordion-icon-left" aria-hidden="true">
															<span class="elementor-accordion-icon-closed"><i class="fas fa-plus"></i></span>
								<span class="elementor-accordion-icon-opened"><i class="fas fa-minus"></i></span>
														</span>
												<a class="elementor-accordion-title" href="">Kell-e regisztrálni rendeléshez?</a>
					</div>
					<div id="elementor-tab-content-1181" class="elementor-tab-content elementor-clearfix elementor-active" data-tab="1" role="tabpanel" aria-labelledby="elementor-tab-title-1181" style="display: block;"><p>Nem kötelező, azonban számos előnnyel jár ha készít egy fiókot a rendeléskor.<br>Pár kattintással jóvá lehet hagyni, hogy felhasználói fiókot is készítsen automatikusan a rendszer. A rendelés folyamán a fiókjában tudja követni a csomag állapotát, következő rendeléseknél pedig már el lesznek mentve az adatai .</p></div>
				</div>
							<div class="elementor-accordion-item">
					<div id="elementor-tab-title-1182" class="elementor-tab-title" data-tab="2" role="tab" aria-controls="elementor-tab-content-1182" aria-expanded="false">
													<span class="elementor-accordion-icon elementor-accordion-icon-left" aria-hidden="true">
															<span class="elementor-accordion-icon-closed"><i class="fas fa-plus"></i></span>
								<span class="elementor-accordion-icon-opened"><i class="fas fa-minus"></i></span>
														</span>
												<a class="elementor-accordion-title" href="">Nem vagyok benne biztos hogy melyik alkatrészt rendeljem…ki segít?</a>
					</div>
					<div id="elementor-tab-content-1182" class="elementor-tab-content elementor-clearfix" data-tab="2" role="tabpanel" aria-labelledby="elementor-tab-title-1182"><p>Ha nem biztos benne, hogy melyik termék lenne önnek megfelelő vegye fel velünk a kapcsolatot a “segíthetünk” gombra kattintva a lap alján és a lehető leghamarabb segítünk a választásban</p></div>
				</div>
							<div class="elementor-accordion-item">
					<div id="elementor-tab-title-1183" class="elementor-tab-title" data-tab="3" role="tab" aria-controls="elementor-tab-content-1183" aria-expanded="false">
													<span class="elementor-accordion-icon elementor-accordion-icon-left" aria-hidden="true">
															<span class="elementor-accordion-icon-closed"><i class="fas fa-plus"></i></span>
								<span class="elementor-accordion-icon-opened"><i class="fas fa-minus"></i></span>
														</span>
												<a class="elementor-accordion-title" href="">Mi a teendő ha mégse jó terméket kaptam?</a>
					</div>
					<div id="elementor-tab-content-1183" class="elementor-tab-content elementor-clearfix" data-tab="3" role="tabpanel" aria-labelledby="elementor-tab-title-1183"><p>Hibás rendelés esetén vagy ha nem elégedett a termékkel, lehetőség van <strong>cserére </strong>vagy <strong>visszaküldésre</strong>. Mindkét esetben emailben kell jelezni az ügyfélszolgálat felé ezt, és felvesszük veled a kapcsolatot.<br>Email cím: info@royaltuning.hu</p></div>
				</div>
							<div class="elementor-accordion-item">
					<div id="elementor-tab-title-1184" class="elementor-tab-title" data-tab="4" role="tab" aria-controls="elementor-tab-content-1184" aria-expanded="false">
													<span class="elementor-accordion-icon elementor-accordion-icon-left" aria-hidden="true">
															<span class="elementor-accordion-icon-closed"><i class="fas fa-plus"></i></span>
								<span class="elementor-accordion-icon-opened"><i class="fas fa-minus"></i></span>
														</span>
												<a class="elementor-accordion-title" href="">Megbízható a webshop? Mire számíthatok?</a>
					</div>
					<div id="elementor-tab-content-1184" class="elementor-tab-content elementor-clearfix" data-tab="4" role="tabpanel" aria-labelledby="elementor-tab-title-1184"><p>A webshop megbízhatóságát a független vásárlói visszajelzésekkel szeretnénk átláthatóvá tenni. Ebben az árukereső a partnerünk, akik kérdőiv formájában minden vásárlónktól kérnek visszajelzést. Ezeket a véleményeket bárki elolvashatja akár évekre visszamenőleg is.</p></div>
				</div>
								</div>
				</div>
				</div>
				<div class="elementor-element elementor-element-4f58bf5c elementor-widget elementor-widget-heading" data-id="4f58bf5c" data-element_type="widget" data-widget_type="heading.default">
				<div class="elementor-widget-container">
			<h2 class="elementor-heading-title elementor-size-default">Fizetéssel kapcsolatos kérdése</h2>		</div>
				</div>
				<div class="elementor-element elementor-element-733e30ae elementor-widget elementor-widget-accordion" data-id="733e30ae" data-element_type="widget" data-widget_type="accordion.default">
				<div class="elementor-widget-container">
					<div class="elementor-accordion" role="tablist">
							<div class="elementor-accordion-item">
					<div id="elementor-tab-title-1931" class="elementor-tab-title elementor-active" data-tab="1" role="tab" aria-controls="elementor-tab-content-1931" aria-expanded="true" tabindex="0" aria-selected="true">
													<span class="elementor-accordion-icon elementor-accordion-icon-left" aria-hidden="true">
															<span class="elementor-accordion-icon-closed"><i class="fas fa-plus"></i></span>
								<span class="elementor-accordion-icon-opened"><i class="fas fa-minus"></i></span>
														</span>
												<a class="elementor-accordion-title" href="">Milyen fizetési módok vannak az oldalon?</a>
					</div>
					<div id="elementor-tab-content-1931" class="elementor-tab-content elementor-clearfix elementor-active" data-tab="1" role="tabpanel" aria-labelledby="elementor-tab-title-1931" style="display: block;"><p><strong><em>Utánvétel</em></strong>, tehát amikor átveszed a csomagot akkor fizeted ki. A futároknál lehet kártyával és kézpéznben is fizetni.<br>Foxpost automatánál csak bankkártyával lehet fizetni.</p><p><strong><em>Online bankkártyás fizetés</em></strong>re is van lehetősés <strong>Barion </strong>és <strong>Paylike </strong>fizetőkapun keresztül. Mindkét lehetőség maximális biztonságot nyújt, ezért is választottuk ezt a két céget.</p><p><strong><em>Banki átutalás</em></strong>, ebben az esetben egy bankszámlaszámra kell elutalni a rendelés értékét, és az összeg beérkezése után feladjuk a csomagot.<br>Az átvételkor már nem kell fizetni, csak átvenni a csomagot</p></div>
				</div>
								</div>
				</div>
				</div>
					</div>';

}


/**
 * Add custom tracking code to the thank-you page
 */
add_action('woocommerce_thankyou', 'my_custom_tracking');

function my_custom_tracking($order_id)
{

    $order = wc_get_order($order_id);

    echo "
    <script>
    (function(t, r, a, c, k, i, n, g) {t['ROIDataObject'] = k;
t[k]=t[k]||function(){(t[k].q=t[k].q||[]).push(arguments)},t[k].c=i;n=r.createElement(a),
g=r.getElementsByTagName(a)[0];n.async=1;n.src=c;g.parentNode.insertBefore(n,g)
})(window, document, 'script', '//www.arukereso.hu/ocm/sdk.js', 'arukereso', 'hu');

arukereso('authenticate', 'WmyMkg9VCqag5o14bU5Gv9mVMk7caZ6NatAt');
arukereso('set_order_id', " . $order->get_id() . ");";

    $line_items = $order->get_items();

    foreach ($line_items as $item) {
        $product = $order->get_product_from_item($item);
        echo "
        arukereso('add_product', " . $product->get_sku() . ", '" . $item->get_name() . "', " . $order->get_line_total($item, true, true) . ", " . $item['qty'] . ");";

    }
    echo "
arukereso('set_total_vat', " . $order->get_total() . ");
arukereso('set_currency', 'HUF');
arukereso('send', 'Order');
    </script>";
}

remove_action('shutdown', 'wp_ob_end_flush_all', 1);
add_action('shutdown', function () {
    while (@ob_end_flush()) ;
});

function subscribe_link()
{
    $servername = "localhost";
    $username = "royaltuning_laravel";
    $password = "popreg-rixne2-gitqIv";
    $dbname = "royaltuning_laravel";

    $conn = mysqli_connect($servername, $username, $password, $dbname);

    if ($conn->connect_errno) {
        echo "Failed to connect to MySQL: " . $conn->connect_error;
        exit();
    }
    printf('Connected successfully.<br />');
    /*
        $args = array(
            'status' => 'publish',
            'orderby' => 'title',
            'order' => 'DESC',
            'limit' => -1,
        );
        $products = wc_get_products($args);
        echo "<pre>";
        //var_dump($products);
        echo "</pre>";
        if (count($products) > 0) {
            $k = 0;
            foreach ($products as $product) {
                $tags = $product->tag_ids;
                $id = $product->get_id();
                $z = 0;
                foreach ($tags as $tag) {

                        $term = get_term($tag);

                        $termId=$term->term_id;
                        $slug=$term->slug;
                        $name=$term->name;
                        $desc=$term->description;


                        $termInsert = "INSERT IGNORE tags(
                             id, slug, created_at, updated_at) VALUES (
                             " . $termId . ",'" . $slug . "','" . date("Y-m-d H:i:s") . "','" . date("Y-m-d H:i:s") . "')";
                        $result2 = mysqli_query($conn, $termInsert);
                        if (!$result2) {
                            echo "<p> Query [$termInsert] couldn't be executed </p>";
                            echo mysqli_error($conn);
                        }


                        $termInsert2 = "INSERT IGNORE tag_translations(tag_id, locale, name, short_desc, long_desc) VALUES (" . $termId . ",'hu','" . $name . "',NULL,'" . addslashes($desc) . "');";
                        $result3 = mysqli_query($conn, $termInsert2);
                        if (!$result3) {
                            echo "<p> Query [$termInsert2] couldn't be executed </p>";
                            echo mysqli_error($conn);
                        }

                        $termInsert3 = "INSERT IGNORE product_tags(product_id, tag_id) VALUES (" . $id . "," . $termId . ");";
                        $result4 = mysqli_query($conn, $termInsert3);
                        if (!$result4) {
                            echo "<p> Query [$termInsert3] couldn't be executed </p>";
                            echo mysqli_error($conn);
                        }

                    $z++;
                }
                $k++;
            }

        }
        */


    $categories = get_terms(
        array(
            'taxonomy' => 'product_cat',
            'orderby' => 'name',
            'hide_empty' => false,
        )
    );
    //$categories = treeify_terms($categories);

    echo "<pre>";
    //var_dump($categories);
    echo "</pre>";
    foreach ($categories as $category) {

        $thumbnail_id = get_woocommerce_term_meta($category->term_id, 'thumbnail_id', true);
        $imageUrl = wp_get_attachment_url($thumbnail_id);
        $id = $category->term_id;

        if ($imageUrl) {
            $image = file_get_contents($imageUrl);
            $extension = pathinfo($imageUrl)['extension'];

            $filename = pathinfo($imageUrl)['filename'] . "." . $extension;
            $file = "/home/royaltuning/public_html/newshop/public/storage/media/" . $filename;
            $mime_type = mime_content_type($file);
            $image_size = round(filesize($file));


            file_put_contents($file, $image);

            $productFeauteredImageInsert = "INSERT IGNORE files (user_id, filename, disk, path, extension, mime, size, created_at) VALUES (1,'" . $filename . "','public_storage','media/" . $filename . "','" . $extension . "','" . $mime_type . "','" . $image_size . "','" . date("Y-m-d H:i:s") . "')";
            $image = mysqli_query($conn, $productFeauteredImageInsert);
            if (!$image) {
                echo "<p> Query [$productFeauteredImageInsert] couldn't be executed </p>";
                echo mysqli_error($conn);
            }

            $featuredImageId = $conn->insert_id;

            if ($featuredImageId == 0) {
                $query = "SELECT * FROM files WHERE filename ='" . $filename . "'";
                $result = mysqli_query($conn, $query);
                $num_results = mysqli_num_rows($result);
                for ($i = 0; $i < $num_results; $i++) {
                    $row = mysqli_fetch_assoc($result);
                    $featuredImageId = $row["id"];
                }

            }

            $productFeauteredImageInsert2 = "INSERT IGNORE entity_files (file_id, entity_type, entity_id, zone, created_at) VALUES (" . $featuredImageId . ", '" . addslashes('Modules\Category\Entities\Category') . "', " . $id . ",'logo', '" . date("Y-m-d H:i:s") . "');";

            $image2 = mysqli_query($conn, $productFeauteredImageInsert2);
            if (!$image2) {
                echo "<p> Query [$productFeauteredImageInsert2] couldn't be executed </p>";
                echo mysqli_error($conn);
            }
        }


    }


    /*
$args = array(
    'status' => 'publish',
    'orderby' => 'title',
    'order' => 'DESC',
    'limit' => -1,
);
$products = wc_get_products($args);
echo "<pre>";
//var_dump($products);
if (count($products) > 0) {
    $z = 0;
    foreach ($products as $product) {
        /*
                        $title = $product->name . " - Royal Tuning Autó és Motoros Kiegészítő Webshop";
                        $desc = RankMath\Post::get_meta('description', $product->get_id());
                        $id = $product->get_id();
                        echo "<br>Meta content:" . $title . "<br>" . $desc;
                        $metaData = "INSERT IGNORE meta_data (entity_type, entity_id, created_at) VALUES ('" . addslashes('Modules\Product\Entities\Product') . "', " . $id . ", '" . date("Y-m-d H:i:s") . "');";

                        $image2 = mysqli_query($conn, $metaData);
                        if (!$image2) {
                            echo "<p> Query [$metaData] couldn't be executed </p>";
                            echo mysqli_error($conn);
                        }
                        $metaDataId = $conn->insert_id;

                        $metaDataTrans = "INSERT IGNORE meta_data_translations (meta_data_id, locale, meta_title, meta_description) VALUES (" . $metaDataId . ",'hu','" . $title . "','" . $desc . "');";
                        $image3 = mysqli_query($conn, $metaDataTrans);
                        if (!$image3) {
                            echo "<p> Query [$metaDataTrans] couldn't be executed </p>";
                            echo mysqli_error($conn);
                        }

                    $z++;

                    $id = $product->get_id();
                    //if ($id == "8072") {
                        //var_dump($product);

                        $imageUrl = wp_get_attachment_url($product->get_image_id());

                        if ($imageUrl) {
                            $image = file_get_contents($imageUrl);
                            $extension = pathinfo($imageUrl)['extension'];

                            $filename = pathinfo($imageUrl)['filename'] . "." . $extension;
                            $file = "/home/royaltuning/public_html/newshop/public/storage/media/" . $filename;
                            $mime_type = mime_content_type($file);
                            $image_size = round(filesize($file));


                            file_put_contents($file, $image);


                            $productFeauteredImageInsert = "INSERT IGNORE files (user_id, filename, disk, path, extension, mime, size, created_at) VALUES (1,'" . $filename . "','public_storage','media/" . $filename . "','" . $extension . "','" . $mime_type . "','" . $image_size . "','" . date("Y-m-d H:i:s") . "')";
                            $imageQuery = mysqli_query($conn, $productFeauteredImageInsert);

                            if (!$imageQuery) {
                                echo "<p> Query [$productFeauteredImageInsert] couldn't be executed </p>";
                                echo mysqli_error($conn);
                            }

                            $featuredImageId = $conn->insert_id;
                            if($featuredImageId==0){
                                $query = "SELECT * FROM files WHERE filename ='".$filename."'";
                                $result = mysqli_query($conn, $query);
                                $num_results = mysqli_num_rows($result);
                                for($i=0; $i<$num_results; $i++) {
                                    $row = mysqli_fetch_assoc($result);
                                    $featuredImageId =$row["id"];
                                }

                            }

                            $productFeauteredImageInsert2 = "INSERT IGNORE entity_files (file_id, entity_type, entity_id, zone, created_at) VALUES (" . $featuredImageId . ", '" . addslashes('Modules\Product\Entities\Product') . "', " . $id . ",'base_image', '" . date("Y-m-d H:i:s") . "');";

                            $image2 = mysqli_query($conn, $productFeauteredImageInsert2);
                            if (!$image2) {
                                echo "<p> Query [$productFeauteredImageInsert2] couldn't be executed </p>";
                                echo mysqli_error($conn);
                            }
                        } else {
                            echo $id . "<br>";
                        }

                        //$image=$product->get_image_id();
                        $gallery_images = $product->get_gallery_image_ids();



                        if (isset($gallery_images[0])) {

                            foreach ($gallery_images as $gallery_image) {


                                $imageUrl = wp_get_attachment_url($gallery_image);

                                if ($imageUrl) {

                                    $image = file_get_contents($imageUrl);
                                    $extension = pathinfo($imageUrl)['extension'];

                                    $filename = pathinfo($imageUrl)['filename'] . "." . $extension;
                                    $file = "/home/royaltuning/public_html/newshop/public/storage/media/" . $filename;
                                    $mime_type = mime_content_type($file);
                                    $image_size = round(filesize($file));


                                    file_put_contents($file, $image);

                                    $productFeauteredImageInsert = "INSERT IGNORE files (user_id, filename, disk, path, extension, mime, size, created_at) VALUES (1,'" . $filename . "','public_storage','media/" . $filename . "','" . $extension . "','" . $mime_type . "','" . $image_size . "','" . date("Y-m-d H:i:s") . "')";
                                    $imageQueryGallery = mysqli_query($conn, $productFeauteredImageInsert);
                                    $featuredImageId = mysqli_insert_id($conn);
                                    if (!$imageQueryGallery) {
                                        echo "<p> Query [$productFeauteredImageInsert] couldn't be executed </p>";
                                        echo mysqli_error($conn);
                                    }

                                    if($featuredImageId==0){
                                        $query = "SELECT * FROM files WHERE filename ='".$filename."'";
                                        $result = mysqli_query($conn, $query);
                                        $num_results = mysqli_num_rows($result);
                                        for($i=0; $i<$num_results; $i++) {
                                            $row = mysqli_fetch_assoc($result);
                                            $featuredImageId =$row["id"];
                                        }

                                    }





                                    $productFeauteredImageInsert2 = "INSERT IGNORE entity_files (file_id, entity_type, entity_id, zone, created_at) VALUES (" . $featuredImageId . ", '" . addslashes('Modules\Product\Entities\Product') . "', " . $id . ",'additional_images', '" . date("Y-m-d H:i:s") . "');";

                                    $imageQueryGallery2 = mysqli_query($conn, $productFeauteredImageInsert2);
                                    if (!$imageQueryGallery2) {
                                        echo "<p> Query [$productFeauteredImageInsert2] couldn't be executed </p>";
                                        echo mysqli_error($conn);
                                    }
                                }

                            }
                        } else {
                            echo $id . "<br>";
                        }
                        //return false;

                        $name = $product->get_name();
                        $price = $product->get_price();
                        $sku = $product->get_sku();
                        $stock = $product->get_stock_quantity() ?? 0;
                        $slug = $product->get_slug();
                        $description = $product->get_description();
                        $short_description = $product->get_short_description();
                        $categories = $product->get_category_ids();

                        $productInsert = "INSERT IGNORE products(
                                     id, brand_id, tax_class_id, slug, price, special_price, special_price_type, special_price_start,
                                     special_price_end, selling_price, sku, manage_stock, qty, in_stock, viewed, is_active, new_from,
                                     new_to, deleted_at, created_at, updated_at, is_virtual) VALUES (
                                     " . $id . ",'1','1','" . $slug . "'," . $price . ",NULL,NULL,NULL,NULL," . $price . ",'" . $sku . "',1," . $stock . ",1,0,0,NULL,NULL,NULL,'" . date("Y-m-d H:i:s") . "',NULL,0)";
                        $result2 = mysqli_query($conn, $productInsert);
                        if (!$result2) {
                            echo "<p> Query [$productInsert] couldn't be executed </p>";
                            echo mysqli_error($conn);
                        }


                        $productInsert2 = "INSERT IGNORE product_translations(product_id, locale, name, description, short_description) VALUES (" . $id . ",'hu','" . $name . "','" . addslashes($description) . "','" . addslashes($short_description) . "');";
                        $result3 = mysqli_query($conn, $productInsert2);
                        if (!$result3) {
                            echo "<p> Query [$productInsert2] couldn't be executed </p>";
                            echo mysqli_error($conn);
                        }

                        foreach ($categories as $category) {

                            //var_dump($category);
                            $categoryInsert = "INSERT IGNORE product_categories (product_id, category_id) VALUES ('$id','$category');";

                            $result = mysqli_query($conn, $categoryInsert);
                            if (!$result) {
                                echo "<p> Query [$categoryInsert] couldn't be executed </p>";
                                echo mysqli_error($conn);
                            }

                        }
                    //}

    }
}
    */
    echo "</pre>";

}

add_shortcode('subscribe', 'subscribe_link');

function calculate_delivery_date() {
    $now = new DateTime('now', new DateTimeZone('Europe/Budapest'));
    $hour = (int) $now->format('H');
    $dayOfWeek = (int) $now->format('N'); // 1 = hétfő, 7 = vasárnap

    // Alapértelmezett ünnepnapok (2025 Magyarország)
    $default_holidays = [
        '2025-01-01', '2025-03-15', '2025-04-18', '2025-04-21', '2025-05-01',
        '2025-08-20', '2025-10-23', '2025-12-25', '2025-12-26'
    ];

    // Ünnepnapok lekérése az adatbázisból
    $custom_holidays = get_option('custom_holidays', []);
    if (!is_array($custom_holidays)) {
        $custom_holidays = [];
    }

    $holidays = array_unique(array_merge($default_holidays, $custom_holidays));

    // Szállítási dátum számítása
    $delivery_date_early = clone $now;
    $delivery_date_late = clone $now;

    // Péntek 14:00 után, szombat és vasárnap esetén következő kedd a szállítás
    if ($dayOfWeek == 6 || $dayOfWeek == 7 || ($dayOfWeek == 5 && $hour >= 14)) {
        $delivery_date_early->modify('next tuesday');
    } else {
        $delivery_date_early->modify('+1 day');
    }

    // Ha kedd ünnepnap, akkor szerdára módosítjuk
    while (in_array($delivery_date_early->format('Y-m-d'), $holidays)) {
        $delivery_date_early->modify('+1 day');
    }

    // Késői szállítás kezelése
    if ($dayOfWeek == 6 || $dayOfWeek == 7 || ($dayOfWeek == 5 && $hour >= 14)) {
        $delivery_date_late->modify('next tuesday');
    } else {
        $delivery_date_late->modify('+2 days');
    }

    // Ha hétvégére esik, akkor a következő hétfőre állítjuk
    if ($delivery_date_early->format('N') >= 6) {
        $delivery_date_early->modify('next monday');
    }
    if ($delivery_date_late->format('N') >= 6) {
        $delivery_date_late->modify('next monday');
    }

    // Ha kedd ünnepnap, akkor szerdára módosítjuk
    while (in_array($delivery_date_late->format('Y-m-d'), $holidays)) {
        $delivery_date_late->modify('+1 day');
    }

    return '<b>Várható szállítás:</b> <br>Mai nap 14:00 előtt leadott rendelés esetén: <span style="color:green; font-weight:700">' . $delivery_date_early->format('Y.m.d.') . '</span><br> Mai nap 14:00 után leadott rendelés esetén: <span style="color:green; font-weight:700">' . $delivery_date_late->format('Y.m.d.').'</span>';
}



function shipping_eta_shortcode() {
    global $post;
    $product_id = isset($atts['product_id']) ? intval($atts['product_id']) : 0;

    if ($product_id === 0 && isset($post->ID)) {
        $product_id = get_the_ID();
    }


    if ($product_id > 0) {
        $productStatus = get_post_meta( $product_id, '_stock_status', true );
        if (!$productStatus || $productStatus!="instock") {
            return ''; // Ha nincs készleten, nem jelenik meg semmi
        }
    }

    return calculate_delivery_date();
}
add_shortcode('shipping_eta', 'shipping_eta_shortcode');

// Admin felület az ünnepnapok szerkesztéséhez
function shipping_eta_admin_menu() {
    add_options_page('Ünnepnapok beállításai', 'Ünnepnapok', 'manage_options', 'shipping_eta_settings', 'shipping_eta_settings_page');
}
add_action('admin_menu', 'shipping_eta_admin_menu');

function shipping_eta_settings_page() {
    if (isset($_POST['custom_holidays'])) {
        $custom_holidays = array_filter(array_map('sanitize_text_field', $_POST['custom_holidays']));
        update_option('custom_holidays', $custom_holidays);
        echo '<div class="updated"><p>Ünnepnapok frissítve!</p></div>';
    }
    $custom_holidays = get_option('custom_holidays', []);

    echo '<div class="wrap">';
    echo '<h1>Ünnepnapok beállítása</h1>';
    echo '<form method="post">';
    echo '<div id="holiday-fields">';
    $default_holidays = [
        '2025-01-01', '2025-03-15', '2025-04-18', '2025-04-21', '2025-05-01',
        '2025-08-20', '2025-10-23', '2025-12-25', '2025-12-26'
    ];
    // Alapértelmezett ünnepnapok listázása törölhetően
    foreach ($default_holidays as $holiday) {
        echo '<div class="holiday-item"><input type="text" name="custom_holidays[]" value="' . esc_attr($holiday) . '" placeholder="YYYY-MM-DD" readonly /> <button type="button" class="remove-holiday button">Törlés</button></div>';
    }

    // Egyedi ünnepnapok listázása törölhetően
    foreach ($custom_holidays as $holiday) {
        echo '<div class="holiday-item"><input type="text" name="custom_holidays[]" value="' . esc_attr($holiday) . '" placeholder="YYYY-MM-DD" /> <button type="button" class="remove-holiday button">Törlés</button></div>';
    }

    echo '</div>';
    echo '<button type="button" id="add-holiday" class="button">Új ünnepnap hozzáadása</button><br><br>';
    echo '<input type="submit" value="Mentés" class="button button-primary">';
    echo '</form>';
    echo '</div>';
    echo '<script>
        document.getElementById("add-holiday").addEventListener("click", function() {
            var container = document.getElementById("holiday-fields");
            var div = document.createElement("div");
            div.className = "holiday-item";
            var input = document.createElement("input");
            input.type = "text";
            input.name = "custom_holidays[]";
            input.placeholder = "YYYY-MM-DD";
            var button = document.createElement("button");
            button.type = "button";
            button.className = "remove-holiday button";
            button.textContent = "Törlés";
            button.onclick = function() { div.remove(); };
            div.appendChild(input);
            div.appendChild(button);
            container.appendChild(div);
        });
        
        document.querySelectorAll(".remove-holiday").forEach(button => {
            button.addEventListener("click", function() {
                this.parentElement.remove();
            });
        });
    </script>';
}
function custom_variation_redirect_add_to_cart($link, $product, $args) {
    if ($product->is_type('variable')) {
        $link = sprintf(
            '<a href="%s" class="button %s">%s</a>',
            esc_url(get_permalink($product->get_id())),
            esc_attr(isset($args['class']) ? $args['class'] : 'button'),
            esc_html__('Válassz opciót', 'woocommerce')
        );
    }
    return $link;
}
add_filter('woocommerce_loop_add_to_cart_link', 'custom_variation_redirect_add_to_cart', 10, 3);

add_action('woocommerce_product_options_general_product_data', 'custom_woo_add_youtube_field');
function custom_woo_add_youtube_field() {
    echo '<div class="options_group">';
    woocommerce_wp_text_input(array(
        'id'          => '_youtube_video_url',
        'label'       => __('YouTube Video URL', 'woocommerce'),
        'placeholder' => 'https://www.youtube.com/watch?v=xyz',
        'desc_tip'    => true,
        'description' => __('Add YouTube video URL here.', 'woocommerce'),
    ));
    echo '</div>';
}

// 2. Egyedi mező mentése
add_action('woocommerce_process_product_meta', 'custom_woo_save_youtube_field');
function custom_woo_save_youtube_field($post_id) {
    $youtube_url = isset($_POST['_youtube_video_url']) ? sanitize_text_field($_POST['_youtube_video_url']) : '';
    update_post_meta($post_id, '_youtube_video_url', $youtube_url);
}

// 3. YouTube script (FancyBox használata popuphoz)
add_action('wp_enqueue_scripts', 'custom_woo_enqueue_scripts');
function custom_woo_enqueue_scripts() {
    if (is_product()) {
        wp_enqueue_script('jquery');
        wp_enqueue_style('fancybox-css', 'https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.css');
        wp_enqueue_script('fancybox-js', 'https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js', ['jquery'], null, true);
    }
}


add_action('admin_enqueue_scripts', function() {
    wp_enqueue_script(
        'royaltuning-admin-pointer-patch',
        get_stylesheet_directory_uri() . '/js/admin-pointer-patch.js',
        ['jquery'],
        '1.0',
        true
    );
});

// 1. Oszlop hozzáadása a terméklistához
add_filter('manage_edit-product_columns', function($columns) {
    $columns['youtube_check'] = 'YouTube Szinkron';
    return $columns;
});

// 2. Oszlop tartalom kitöltése
add_action('manage_product_posts_custom_column', function($column, $post_id) {
    if ($column === 'youtube_check') {
        $product = wc_get_product($post_id);
        $description = $product->get_description();
        $meta_url = get_post_meta($post_id, '_youtube_video_url', true);

        // YouTube link keresése
        $has_youtube = preg_match('/https?:\/\/(www\.)?(youtube\.com|youtu\.be)\/[^\s"]+/', $description);

        if ($has_youtube && empty($meta_url)) {
            echo '<span style="color: red; font-weight: bold;">⚠️ Hiányzó meta!</span>';
        } elseif ($has_youtube) {
            echo '<span style="color: green;">✅ Ok</span>';
        } else {
            echo '<span style="color: gray;">—</span>';
        }
    }
}, 10, 2);

add_filter('woocommerce_product_tabs', 'remove_reviews_from_tabs', 98);
function remove_reviews_from_tabs($tabs) {
    unset($tabs['reviews']);
    return $tabs;
}
add_shortcode('product_reviews', function() {
    if (comments_open()) {
        ob_start();
        comments_template();
        echo " <style>
        #reviews{
        border:2px solid #34A853 !important;
        border-radius:5px;
        padding:10px;
        }
        </style>";

        return ob_get_clean();
    }
});

add_action('init', function () {
    $order_id = 300276; // a rendelés ID-je
    $order = wc_get_order($order_id);
    if ($order) {
        $order->set_currency('HUF');
        $order->save();
    }
});

//Sociallion codes

add_shortcode( 'al_subcat_grid', function( $atts = [] ) {
    if ( ! class_exists( 'WooCommerce' ) ) return '';

    // Attributes
    $atts = shortcode_atts( [
        'cols_desktop' => '4',   // 1–6
        'cols_tablet'  => '3',   // 1–6
        'cols_mobile'  => '2',   // 1–6
        'gap'          => '16',  // 8,12,16,20,24,32 (px)
        'hide_empty'   => 'yes',
        'show_on_shop' => 'yes',
        'sort_by'      => 'title',     // title|count|id|slug|term_order|menu_order
        'sort_order'   => 'ASC',       // ASC|DESC
        'include'      => '',          // comma-separated term IDs
        'exclude'      => '',          // comma-separated term IDs
        'limit'        => '',          // integer
        'show_count'   => 'yes',       // yes|no
        'img_size'     => 'woocommerce_thumbnail', // any registered image size
        'cache'        => 'yes',       // yes|no
    ], $atts, 'al_subcat_grid' );

    // Sanitize + clamp
    $clamp = function( $n ){ $n = (int) $n; return max( 1, min( 6, $n ) ); };
    $cols_mobile  = $clamp( $atts['cols_mobile'] );
    $cols_tablet  = $clamp( $atts['cols_tablet'] );
    $cols_desktop = $clamp( $atts['cols_desktop'] );

    $allowed_gaps = [ 8, 12, 16, 20, 24, 32 ];
    $gap = (int) $atts['gap'];
    if ( ! in_array( $gap, $allowed_gaps, true ) ) $gap = 16;

    $hide_empty    = strtolower( $atts['hide_empty'] )   === 'yes';
    $show_on_shop  = strtolower( $atts['show_on_shop'] ) === 'yes';
    $show_count    = strtolower( $atts['show_count'] )   === 'yes';
    $use_cache     = strtolower( $atts['cache'] )        === 'yes';

    $orderby_in = strtolower( $atts['sort_by'] );
    $orderby = in_array( $orderby_in, [ 'title','count','id','slug','term_order','menu_order' ], true ) ? $orderby_in : 'title';
    if ( $orderby === 'term_order' ) $orderby = 'menu_order';
    $order   = strtoupper( $atts['sort_order'] ) === 'DESC' ? 'DESC' : 'ASC';

    // Validate img_size against registered sizes; fallback to 'woocommerce_thumbnail'
    $img_size = is_string( $atts['img_size'] ) && $atts['img_size'] !== '' ? $atts['img_size'] : 'woocommerce_thumbnail';
    $registered_sizes = wp_get_registered_image_subsizes();
    if ( $img_size && ! isset( $registered_sizes[ $img_size ] ) ) {
        // allow numeric WxH like "300x300"
        if ( preg_match( '/^\d+x\d+$/', $img_size ) ) {
            // keep custom size string for sizes attr but wp_get_attachment_image requires name or array(width,height)
            list($w, $h) = array_map( 'intval', explode( 'x', $img_size ) );
            $img_size = [ max(1,$w), max(1,$h) ];
        } else {
            $img_size = 'woocommerce_thumbnail';
        }
    }

    $taxonomy = 'product_cat';
    $parent_term_id = 0;

    $q = get_queried_object();
    if ( $q && isset( $q->taxonomy ) && $q->taxonomy === $taxonomy ) {
        $parent_term_id = (int) $q->term_id;
    } elseif ( function_exists( 'is_shop' ) && is_shop() && $show_on_shop ) {
        $parent_term_id = 0; // top-level
    } else {
        return '';
    }

    // Build get_terms args
    $args = [
        'taxonomy'   => $taxonomy,
        'parent'     => $parent_term_id,
        'hide_empty' => $hide_empty,
        'orderby'    => $orderby,
        'order'      => $order,
        'fields'     => 'all',
    ];

    // Include / exclude
    if ( trim( $atts['include'] ) !== '' ) {
        $include = array_filter( array_map( 'absint', explode( ',', $atts['include'] ) ) );
        if ( $include ) {
            $args['include'] = $include;
        }
    }
    if ( trim( $atts['exclude'] ) !== '' ) {
        $exclude = array_filter( array_map( 'absint', explode( ',', $atts['exclude'] ) ) );
        if ( $exclude ) $args['exclude'] = $exclude;
    }
    if ( is_numeric( $atts['limit'] ) && (int) $atts['limit'] > 0 ) {
        $args['number'] = (int) $atts['limit'];
    }

    $args = apply_filters( 'al_subcat_grid/get_terms_args', $args, $atts );

    // Transient cache
    $cache_key = '';
    if ( $use_cache ) {
        $cache_key = 'al_subcat_grid_' . md5( $parent_term_id . '|' . wp_json_encode( $args ) );
        $terms = get_transient( $cache_key );
    } else {
        $terms = false;
    }
    if ( false === $terms ) {
        $terms = get_terms( $args );
        if ( $use_cache && ! is_wp_error( $terms ) ) {
            set_transient( $cache_key, $terms, HOUR_IN_SECONDS );
        }
    }
    if ( is_wp_error( $terms ) || empty( $terms ) ) return '';

    // Output CSS once
    static $css_done = false;
    ob_start();
    if ( ! $css_done ) : $css_done = true; ?>
        <style>

        </style>
    <?php endif; ?>

    <div class="al-subcat-grid" role="region" aria-label="<?php echo esc_attr( sprintf( __( 'Subcategories of %s', 'your-textdomain' ), $parent_term_id ? get_term( $parent_term_id )->name : __( 'Shop', 'your-textdomain' ) ) ); ?>">
        <div class="al-grid <?php echo esc_attr( 'm-cols-' . $cols_mobile . ' t-cols-' . $cols_tablet . ' d-cols-' . $cols_desktop . ' gap-' . $gap ); ?>">
            <?php foreach ( $terms as $term ) :
                $term_link = get_term_link( $term, $taxonomy );
                if ( is_wp_error( $term_link ) ) continue;

                $thumb_id = (int) get_term_meta( $term->term_id, 'thumbnail_id', true );
                $img_html = '';
                if ( $thumb_id ) {
                    $img_html = wp_get_attachment_image(
                        $thumb_id,
                        $img_size,
                        false,
                        [
                            'class'    => 'al-thumb',
                            'alt'      => $term->name,
                            'loading'  => 'lazy',
                            'decoding' => 'async',

                        ]
                    );
                } elseif ( function_exists( 'wc_placeholder_img_src' ) ) {
                    // Respect requested size when possible; wc_placeholder_img_src only takes a name, so fallback to default
                    $placeholder_src = function_exists( 'wc_placeholder_img_src' ) ? wc_placeholder_img_src( is_string($img_size) ? $img_size : 'woocommerce_thumbnail' ) : '';
                    $img_html = $placeholder_src ? sprintf(
                        '<img class="al-thumb" src="%s" alt="%s" loading="lazy" decoding="async" />',
                        esc_url( $placeholder_src ),
                        esc_attr( $term->name )
                    ) : '';
                }

                $count = (int) $term->count;
                ?>
                <a class="al-card" href="<?php echo esc_url( $term_link ); ?>" aria-label="<?php echo esc_attr( $term->name ); ?>">
                    <?php if ( $img_html ) echo $img_html; ?>
                    <div class="al-content">
                        <h3 class="al-title"><?php echo esc_html( $term->name ); ?></h3>
                        <?php if ( $show_count && $count > 0 ) : ?>
                            <span class="al-count" aria-label="<?php echo esc_attr( sprintf( _n( '%s product', '%s products', $count, 'your-textdomain' ), number_format_i18n( $count ) ) ); ?>">
							<?php echo esc_html( number_format_i18n( $count ) ); ?>
						</span>
                        <?php endif; ?>
                    </div>
                </a>
            <?php endforeach; ?>
        </div>
    </div>
    <?php
    $html = ob_get_clean();
    return $html;
} );


/**
 * Plugin Name: AL — Woo + Elementor Product Filter Bar (Multi-Grid Scoped + AJAX Rehydrate + Modern Preloader)
 * Description: Filter bar for Woo/Elementor Loop Grid. Per-instance namespacing so multiple filter bars & grids on the same page work independently. Archive-aware, preserves params, rehydrates Elementor, accessible UI. Includes optional "source" (e.g. sale) and per-QueryID default source mapping for first-paint correctness. Brand logos, empty-state messages, and performance/a11y improvements included.
 * Version: 1.8.4
 * Author: Art & Living
 * Text Domain: al-woo-ele-filter
 */

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

/** =========================
 *  Dynamic Elementor Query ID registry
 *  ========================= */
if ( ! function_exists( 'al_pf_register_elementor_query_id' ) ) {
    function al_pf_register_elementor_query_id( $ids ) {
        static $registered = array();
        $ids = is_array( $ids ) ? $ids : array( $ids );
        foreach ( $ids as $raw ) {
            $id = sanitize_key( $raw );
            if ( ! $id || isset( $registered[ $id ] ) ) continue;
            add_action( "elementor/query/{$id}",            'al_pf_elementor_posts_query', 5 );
            add_action( "elementor_pro/posts/query/{$id}",  'al_pf_elementor_posts_query', 5 );
            $registered[ $id ] = true;
        }
    }
}
// Defaults for compatibility (+ pre-register sale_query_id to ensure early hook availability)
al_pf_register_elementor_query_id( array( 'archive_loop_grid', 'alpf', 'sale_query_id' ) );

/** =========================
 *  Param helpers (namespaced)
 *  ========================= */
if ( ! function_exists( 'alpf_get_param' ) ) {
    function alpf_get_param( $key, $ns = '' ) {
        $key = (string) $key;
        $ns  = $ns ? sanitize_key( $ns ) : '';
        if ( $ns ) {
            $nk = "{$ns}_{$key}";
            if ( isset( $_GET[ $nk ] ) && $_GET[ $nk ] !== '' ) {
                return $_GET[ $nk ];
            }
        }
        return isset( $_GET[ $key ] ) ? $_GET[ $key ] : null;
    }
}
if ( ! function_exists( 'alpf_get_param_list' ) ) {
    function alpf_get_param_list( $key, $ns = '' ) {
        $val = alpf_get_param( $key, $ns );
        if ( $val === null || $val === '' ) return array();
        $arr = array_map( 'trim', explode( ',', (string) wp_unslash( $val ) ) );
        return array_values( array_filter( $arr, 'strlen' ) );
    }
}

/** =========================
 *  Helpers
 *  ========================= */
if ( ! function_exists( 'al_pf_get_brand_taxonomy' ) ) {
    function al_pf_get_brand_taxonomy( $preferred = '' ) {
        $preferred = $preferred ? sanitize_key( $preferred ) : '';
        if ( $preferred && taxonomy_exists( $preferred ) ) return $preferred;
        if ( taxonomy_exists( 'product_brand' ) )       return 'product_brand';
        if ( taxonomy_exists( 'yith_product_brand' ) )  return 'yith_product_brand';
        if ( taxonomy_exists( 'pa_brand' ) )            return 'pa_brand';
        return '';
    }
}

/** Detect namespaced/un-namespaced PF params anywhere in query string */
if ( ! function_exists( 'alpf_request_has_pf_params_globally' ) ) {
    function alpf_request_has_pf_params_globally() {
        foreach ( (array) $_GET as $k => $v ) {
            if ( ! is_string( $k ) ) continue;
            if ( preg_match( '/(?:^|_)pf_(?:cat|brand|attr|brand_tax|source|min_price|max_price)\z/', $k ) ) {
                if ( $v !== '' && $v !== null ) return true;
            }
        }
        return false;
    }
}

/** =========================
 *  Brand/logo helpers
 *  ========================= */
if ( ! function_exists( 'al_pf_get_term_logo_url' ) ) {
    /**
     * Resolve a brand term's logo URL using common meta keys.
     * Filters:
     *  - alpf_brand_logo_meta_keys : array of possible meta keys for logo attachment id
     *  - alpf_brand_logo_url       : override the resolved URL
     */
    function al_pf_get_term_logo_url( $term_id ) {
        $keys = apply_filters( 'alpf_brand_logo_meta_keys', array(
            'thumbnail_id',
            'brand_thumbnail_id',
            'brand_logo_id',
            'logo_id',
        ) );
        $url = '';
        foreach ( (array) $keys as $meta_key ) {
            $att_id = absint( get_term_meta( $term_id, $meta_key, true ) );
            if ( $att_id ) {
                $src = wp_get_attachment_image_src( $att_id, 'thumbnail' );
                if ( $src && ! empty( $src[0] ) ) { $url = $src[0]; break; }
            }
        }
        return (string) apply_filters( 'alpf_brand_logo_url', $url, $term_id );
    }
}

/** =========================
 *  Source handling (sale / all) — URL param or default map
 *  ========================= */
if ( ! function_exists( 'alpf_get_effective_source' ) ) {
    function alpf_get_effective_source( $ns = '' ) {
        $ns = $ns ? sanitize_key( $ns ) : '';
        $src = alpf_get_param( 'pf_source', $ns );
        if ( $src !== null && $src !== '' ) {
            return sanitize_key( wp_unslash( $src ) );
        }
        $map = apply_filters( 'alpf_default_source_map', array() );
        if ( is_array( $map ) ) {
            if ( $ns && ! empty( $map[ $ns ] ) ) return sanitize_key( $map[ $ns ] );
            if ( isset( $map[''] ) && $map[''] ) return sanitize_key( $map[''] );
        }
        return '';
    }
}

if ( ! function_exists( 'al_pf_get_sale_ids' ) ) {
    function al_pf_get_sale_ids() {
        // Cached for performance on large catalogs
        $cache_key = 'alpf_sale_ids_v1';
        $cached    = get_transient( $cache_key );
        if ( is_array( $cached ) ) return $cached;

        $ids = function_exists( 'wc_get_product_ids_on_sale' ) ? wc_get_product_ids_on_sale() : array();
        $ids = is_array( $ids ) ? array_map( 'intval', $ids ) : array();

        $ttl = (int) apply_filters( 'alpf_sale_ids_ttl', 5 * MINUTE_IN_SECONDS );
        set_transient( $cache_key, $ids, $ttl );
        return $ids;
    }
}

if ( ! function_exists( 'al_pf_apply_source_to_args' ) ) {
    function al_pf_apply_source_to_args( $args, $ns = '' ) {
        $src = alpf_get_effective_source( $ns );
        if ( $src === 'sale' ) {
            $sale_ids = al_pf_get_sale_ids();
            if ( ! empty( $sale_ids ) ) {
                $args['post__in'] = $sale_ids;
            }
        }
        return $args;
    }
}

/** =========================
 *  Query parts
 *  ========================= */
if ( ! function_exists( 'al_pf_build_tax_query_parts' ) ) {
    function al_pf_build_tax_query_parts( $brand_tax = '', $ns = '' ) {
        $parts = array();

        $cats_raw = alpf_get_param_list( 'pf_cat', $ns );
        if ( $cats_raw ) {
            $cats = array_map( 'sanitize_title', $cats_raw );
            if ( $cats ) $parts[] = array( 'taxonomy'=>'product_cat', 'field'=>'slug', 'terms'=>$cats, 'operator'=>'IN' );
        }

        if ( ! $brand_tax ) {
            $brand_tax_raw = alpf_get_param( 'pf_brand_tax', $ns );
            if ( ! empty( $brand_tax_raw ) ) {
                $brand_tax = sanitize_key( wp_unslash( $brand_tax_raw ) );
                if ( ! taxonomy_exists( $brand_tax ) ) $brand_tax = '';
            }
            if ( ! $brand_tax ) $brand_tax = al_pf_get_brand_taxonomy();
        }

        $brands_raw = alpf_get_param_list( 'pf_brand', $ns );
        if ( $brand_tax && $brands_raw ) {
            $brands = array_map( 'sanitize_title', $brands_raw );
            if ( $brands ) $parts[] = array( 'taxonomy'=>$brand_tax, 'field'=>'slug', 'terms'=>$brands, 'operator'=>'IN' );
        }

        $attr_raw = alpf_get_param_list( 'pf_attr', $ns );
        if ( $attr_raw ) {
            $attr_terms_by_tax = array();
            foreach ( $attr_raw as $p ) {
                if ( strpos( $p, ':' ) === false ) continue;
                list( $tax, $slug ) = array_map( 'sanitize_key', explode( ':', $p, 2 ) );
                if ( $tax && $slug && taxonomy_exists( $tax ) ) $attr_terms_by_tax[ $tax ][] = $slug;
            }
            foreach ( $attr_terms_by_tax as $tax => $slugs ) {
                $parts[] = array(
                    'taxonomy' => $tax,
                    'field'    => 'slug',
                    'terms'    => array_values( array_unique( $slugs ) ),
                    'operator' => 'IN',
                );
            }
        }

        return $parts;
    }
}

if ( ! function_exists( 'alpf_build_catalog_guards' ) ) {
    function alpf_build_catalog_guards( $is_search = false, $ns = '' ) {
        $tax_query  = array();
        $meta_query = array();

        $vis_terms = array( 'exclude-from-catalog' );
        if ( $is_search ) $vis_terms[] = 'exclude-from-search';
        $tax_query[] = array(
            'taxonomy' => 'product_visibility',
            'field'    => 'name',
            'terms'    => $vis_terms,
            'operator' => 'NOT IN',
        );

        if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
            $meta_query[] = array(
                'key'     => '_stock_status',
                'value'   => 'outofstock',
                'compare' => '!=',
            );
            $tax_query[] = array(
                'taxonomy' => 'product_visibility',
                'field'    => 'name',
                'terms'    => array( 'outofstock' ),
                'operator' => 'NOT IN',
            );
        }

        // Safer price bounds
        $min_price_raw = alpf_get_param( 'min_price', $ns );
        $max_price_raw = alpf_get_param( 'max_price', $ns );
        $has_min = ($min_price_raw !== null && $min_price_raw !== '');
        $has_max = ($max_price_raw !== null && $max_price_raw !== '');
        if ( $has_min || $has_max ) {
            $min = $has_min ? max( 0, floatval( wp_unslash( $min_price_raw ) ) ) : 0;
            $max = $has_max ? max( $min, floatval( wp_unslash( $max_price_raw ) ) ) : $min;
            $meta_query[] = array(
                'key'     => '_price',
                'type'    => 'DECIMAL',
                'compare' => 'BETWEEN',
                'value'   => array( (string) $min, (string) $max ),
            );
        }

        return array( $tax_query, $meta_query );
    }
}

/** =========================
 *  Collect terms for UI (respects effective source)
 *  ========================= */
if ( ! function_exists( 'al_pf_collect_terms_for_filters' ) ) {
    function al_pf_collect_terms_for_filters( $brand_tax, $attr_tax_slugs, $context = array(), $ns = '' ) {
        $s_query    = alpf_get_param( 's', $ns );
        $s_query    = $s_query ? sanitize_text_field( wp_unslash( $s_query ) ) : '';

        $pf_cat_s   = alpf_get_param( 'pf_cat', $ns );
        $pf_brand_s = alpf_get_param( 'pf_brand', $ns );
        $pf_attr_s  = alpf_get_param( 'pf_attr', $ns );

        $ctx_tax   = '';
        $ctx_terms = array();
        if ( empty( $context ) ) {
            if ( function_exists( 'is_tax' ) && is_tax() ) {
                $qo = get_queried_object();
                if ( $qo && ! empty( $qo->taxonomy ) && taxonomy_exists( $qo->taxonomy ) && is_object_in_taxonomy( 'product', $qo->taxonomy ) ) {
                    $ctx_tax   = $qo->taxonomy;
                    $ctx_terms = array( $qo->slug );
                }
            }
        } else {
            $ctx_tax   = ! empty( $context['tax'] )   ? sanitize_key( $context['tax'] ) : '';
            $ctx_terms = ! empty( $context['terms'] ) ? array_filter( array_map( 'sanitize_title', (array) $context['terms'] ) ) : array();
        }

        $pf_cats   = array_filter( array_map( 'sanitize_title', array_map( 'trim', explode( ',', (string) $pf_cat_s ) ) ) );
        $pf_brands = array_filter( array_map( 'sanitize_title', array_map( 'trim', explode( ',', (string) $pf_brand_s ) ) ) );

        $attr_terms_by_tax = array();
        if ( $pf_attr_s ) {
            foreach ( array_map( 'trim', explode( ',', (string) $pf_attr_s ) ) as $p ) {
                if ( strpos( $p, ':' ) === false ) continue;
                list( $tax, $slug ) = array_map( 'sanitize_key', explode( ':', $p, 2 ) );
                if ( $tax && $slug && taxonomy_exists( $tax ) ) $attr_terms_by_tax[ $tax ][] = $slug;
            }
        }

        $tax_query = array();
        if ( $ctx_tax && $ctx_terms ) {
            $tax_query[] = array(
                'taxonomy' => $ctx_tax,
                'field'    => 'slug',
                'terms'    => $ctx_terms,
                'operator' => 'IN',
            );
        }
        if ( $pf_cats )   $tax_query[] = array( 'taxonomy'=>'product_cat', 'field'=>'slug', 'terms'=>$pf_cats, 'operator'=>'IN' );
        if ( $brand_tax && $pf_brands ) $tax_query[] = array( 'taxonomy'=>$brand_tax, 'field'=>'slug', 'terms'=>$pf_brands, 'operator'=>'IN' );
        if ( $attr_terms_by_tax ) {
            foreach ( $attr_terms_by_tax as $tax => $slugs ) {
                $tax_query[] = array(
                    'taxonomy' => $tax,
                    'field'=>'slug',
                    'terms'    => array_values( array_unique( $slugs ) ),
                    'operator' => 'IN',
                );
            }
        }
        if ( count( $tax_query ) > 1 ) $tax_query = array_merge( array( 'relation' => 'AND' ), $tax_query );

        list( $guard_tax, $guard_meta ) = alpf_build_catalog_guards( ! empty( $s_query ), $ns );

        $extra_key_bits = (string) apply_filters( 'alpf_transient_key_extra', '' );

        $cache_key = 'al_pf_terms_' . md5( wp_json_encode( array(
                    'ns' => $ns,
                    's' => $s_query,
                    'cats' => $pf_cats,
                    'brand_tax' => $brand_tax,
                    'brands' => $pf_brands,
                    'attrs' => $attr_terms_by_tax,
                    'ctx' => array( 'tax' => $ctx_tax, 'terms' => $ctx_terms ),
                    'expose' => array_values( array_unique( (array) $attr_tax_slugs ) ),
                    'source' => (string) alpf_get_effective_source($ns),
                    'ver' => 'v24-mobile-no-overlay-gap'
                ) ) . '|' . $extra_key_bits );
        $cached = get_transient( $cache_key );
        if ( is_array( $cached ) ) return $cached;

        $final_tax = array_merge( (array) $tax_query, (array) $guard_tax );
        if ( count( $final_tax ) > 1 && ! isset( $final_tax['relation'] ) ) {
            $final_tax = array_merge( array( 'relation' => 'AND' ), $final_tax );
        }

        // Optional SKU search (off by default)
        $include_sku = (bool) apply_filters( 'alpf_scan_include_sku', false );
        $scan_meta   = $guard_meta;
        if ( $s_query && $include_sku ) {
            $scan_meta = array_merge(
                array( 'relation' => 'AND' ),
                (array) $guard_meta,
                array(
                    array(
                        'relation' => 'OR',
                        array(
                            'key'     => '_sku',
                            'value'   => $s_query,
                            'compare' => 'LIKE',
                        ),
                    )
                )
            );
        }

        $scan_args = array(
            'post_type'              => 'product',
            'post_status'            => 'publish',
            's'                      => $s_query,
            'fields'                 => 'ids',
            'posts_per_page'         => (int) apply_filters( 'al_pf_terms_scan_posts_cap', 5000 ),
            'no_found_rows'          => true,
            'ignore_sticky_posts'    => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'cache_results'          => false,
            'orderby'                => 'none',
            'tax_query'              => $final_tax,
            'meta_query'             => $scan_meta,
        );

        // Apply "effective source" (e.g., sale) for term scan
        $scan_args = al_pf_apply_source_to_args( $scan_args, $ns );

        $scan = new WP_Query( $scan_args );
        $ids = ! is_wp_error( $scan ) ? array_map( 'intval', $scan->posts ) : array();

        $max_ids = (int) apply_filters( 'al_pf_terms_scan_cap', 5000 );
        if ( count( $ids ) > $max_ids ) {
            $out = array( 'cats'=>array(), 'brands'=>array(), 'attrs'=>array() );
            set_transient( $cache_key, $out, (int) apply_filters( 'al_pf_terms_ttl', 30 ) );
            if ( ! function_exists( 'alpf_index_transient' ) ) { function alpf_index_transient( $k ){} }
            alpf_index_transient( $cache_key );
            return $out;
        }

        $out = array( 'cats' => array(), 'brands' => array(), 'attrs' => array() );
        if ( empty( $ids ) ) {
            set_transient( $cache_key, $out, (int) apply_filters( 'al_pf_terms_ttl', 30 ) );
            if ( ! function_exists( 'alpf_index_transient' ) ) { function alpf_index_transient( $k ){} }
            alpf_index_transient( $cache_key );
            return $out;
        }

        /**
         * UPDATED BEHAVIOR (v1.8.4):
         * - On a parent product_cat archive: show only the direct children that actually have matching products.
         * - On a sub-category archive: show only that sub-category itself (no siblings).
         * - Outside product_cat contexts: fallback to original "all categories from scanned products".
         */
        $cats = array();
        $did_scope_to_children = false;

        if ( $ctx_tax === 'product_cat' && ! empty( $ctx_terms ) && count( $ctx_terms ) === 1 ) {
            $current_term = get_term_by( 'slug', $ctx_terms[0], 'product_cat' );

            if ( $current_term && ! is_wp_error( $current_term ) ) {
                // SUB-CATEGORY: show only this sub-category name
                if ( isset( $current_term->parent ) && (int) $current_term->parent > 0 ) {
                    $cats = array( $current_term );
                    $did_scope_to_children = true;
                } else {
                    // PARENT CATEGORY: show only direct children with products
                    $args = array(
                        'taxonomy'   => 'product_cat',
                        'parent'     => (int) $current_term->term_id,
                        'orderby'    => 'name',
                        'order'      => 'ASC',
                        'hide_empty' => true,
                    );
                    if ( ! empty( $ids ) ) {
                        $args['object_ids'] = $ids; // bind to matched products
                    }
                    $children = get_terms( $args );
                    if ( ! is_wp_error( $children ) && ! empty( $children ) ) {
                        $cats = $children;
                        $did_scope_to_children = true;
                    } else {
                        // No children with products -> empty list (UI shows empty-state)
                        $cats = array();
                        $did_scope_to_children = true;
                    }
                }
            }
        }

        // Fallback ONLY when not on product_cat context
        if ( ! $did_scope_to_children ) {
            $cats = wp_get_object_terms( $ids, 'product_cat', array( 'hide_empty' => false ) );
        }

        if ( ! is_wp_error( $cats ) && $cats ) $out['cats'] = $cats;

        if ( $brand_tax ) {
            $brands = wp_get_object_terms( $ids, $brand_tax, array( 'hide_empty' => false ) );
            if ( ! is_wp_error( $brands ) && $brands ) $out['brands'] = $brands;
        }

        $attr_tax_slugs = array_values( array_unique( (array) $attr_tax_slugs ) );
        foreach ( $attr_tax_slugs as $tax ) {
            $terms = wp_get_object_terms( $ids, $tax, array( 'hide_empty' => false ) );
            if ( ! is_wp_error( $terms ) && $terms ) $out['attrs'][ $tax ] = $terms;
        }

        set_transient( $cache_key, $out, (int) apply_filters( 'al_pf_terms_ttl', 30 ) );
        if ( ! function_exists( 'alpf_index_transient' ) ) { function alpf_index_transient( $k ){} }
        alpf_index_transient( $cache_key );
        return $out;
    }
}

/** =========================
 *  Shortcode (UI)
 *  =========================
 * query_id, param_ns, source, label overrides...
 */
if ( ! function_exists( 'al_pf_filter_bar_shortcode' ) ) {
    function al_pf_filter_bar_shortcode( $atts ) {
        if ( ! class_exists( 'WooCommerce' ) ) return '<!-- WooCommerce not active -->';

        $atts = shortcode_atts( array(
            'brand_tax'        => '',
            'attributes'       => 'all',
            'show_reset'       => 'yes',
            'target'           => '',
            'query_id'         => '',
            'param_ns'         => '',
            'source'           => 'all', // 'all' (default) or 'sale'
            'label_category'   => '',
            'label_brand'      => '',
            'label_attributes' => '',
            'label_reset'      => '',
        ), $atts, 'wc_filter_bar' );

        // Register custom Query IDs
        $query_ids = array();
        if ( ! empty( $atts['query_id'] ) ) {
            foreach ( array_map( 'trim', explode( ',', (string) $atts['query_id'] ) ) as $qid ) {
                $k = sanitize_key( $qid );
                if ( $k ) $query_ids[] = $k;
            }
            if ( $query_ids ) al_pf_register_elementor_query_id( $query_ids );
        }

        // Determine namespace for this instance (param_ns > first query_id > '')
        $ns = '';
        if ( ! empty( $atts['param_ns'] ) ) {
            $ns = sanitize_key( $atts['param_ns'] );
        } elseif ( ! empty( $query_ids ) ) {
            $ns = $query_ids[0];
        }

        // Auto-target: if no 'target' given but we have a single query_id, assume CSS ID equals query_id
        $target_attr = trim( (string) $atts['target'] );
        if ( ! $target_attr && ! empty( $query_ids ) ) {
            $target_attr = '#' . $query_ids[0];
        }

        $forced_brand_tax = $atts['brand_tax'] ? sanitize_key( $atts['brand_tax'] ) : '';
        $brand_tax        = al_pf_get_brand_taxonomy( $forced_brand_tax );

        $attr_tax_slugs = array();
        if ( 'all' === strtolower( $atts['attributes'] ) ) {
            if ( function_exists( 'wc_get_attribute_taxonomies' ) ) {
                $global_attrs = wc_get_attribute_taxonomies();
                if ( $global_attrs ) {
                    foreach ( $global_attrs as $ga ) {
                        $tax = 'pa_' . $ga->attribute_name;
                        if ( taxonomy_exists( $tax ) ) $attr_tax_slugs[] = $tax;
                    }
                }
            }
        } else {
            foreach ( explode( ',', $atts['attributes'] ) as $maybe ) {
                $tax = sanitize_key( trim( $maybe ) );
                if ( $tax && taxonomy_exists( $tax ) ) $attr_tax_slugs[] = $tax;
            }
        }
        $attr_tax_slugs = array_values( array_unique( $attr_tax_slugs ) );

        // Context (taxonomy archive)
        $ctx_tax   = '';
        $ctx_terms = array();
        if ( function_exists('is_tax') && is_tax() ) {
            $qo = get_queried_object();
            if ( $qo && ! empty( $qo->taxonomy ) && taxonomy_exists( $qo->taxonomy ) && is_object_in_taxonomy( 'product', $qo->taxonomy ) ) {
                $ctx_tax   = $qo->taxonomy;
                $ctx_terms = array( $qo->slug );
            }
        }

        $present = al_pf_collect_terms_for_filters( $brand_tax, $attr_tax_slugs, array(
            'tax'   => $ctx_tax,
            'terms' => $ctx_terms,
        ), $ns );

        $sel_cat_s     = alpf_get_param( 'pf_cat',  $ns );
        $sel_brand_s   = alpf_get_param( 'pf_brand',$ns );
        $sel_attr_s    = alpf_get_param( 'pf_attr', $ns );
        $sel_cats      = $sel_cat_s  ? array_filter( array_map( 'sanitize_title', array_map( 'trim', explode( ',', (string) $sel_cat_s ) ) ) ) : array();
        $sel_brands    = $sel_brand_s? array_filter( array_map( 'sanitize_title', array_map( 'trim', explode( ',', (string) $sel_brand_s ) ) ) ) : array();
        $sel_attrs     = $sel_attr_s ? array_filter( array_map( 'sanitize_key',  array_map( 'trim', explode( ',', (string) $sel_attr_s ) ) ) )     : array();

        $sel_brand_tax = $brand_tax ? $brand_tax : ( alpf_get_param( 'pf_brand_tax', $ns ) ? sanitize_key( wp_unslash( alpf_get_param( 'pf_brand_tax', $ns ) ) ) : '' );
        $search_term   = alpf_get_param( 's', $ns );
        $search_term   = $search_term ? sanitize_text_field( wp_unslash( $search_term ) ) : get_search_query();

        $attr_total_terms = 0;
        foreach ( (array) $present['attrs'] as $terms ) { if ( is_array( $terms ) ) $attr_total_terms += count( $terms ); }

        $instance_id = 'alpf-' . wp_generate_uuid4();
        $nonce       = wp_create_nonce( 'alpf_terms' );

        // ARIA ids
        $cats_dd_id   = $instance_id . '-cats';
        $brand_dd_id  = $instance_id . '-brands';
        $attrs_dd_id  = $instance_id . '-attrs';

        // Label overrides (i18n)
        $label_category   = $atts['label_category']   !== '' ? sanitize_text_field( wp_unslash( $atts['label_category'] ) )   : __( 'Category', 'al-woo-ele-filter' );
        $label_brand      = $atts['label_brand']      !== '' ? sanitize_text_field( wp_unslash( $atts['label_brand'] ) )      : __( 'Brand', 'al-woo-ele-filter' );
        $label_attributes = $atts['label_attributes'] !== '' ? sanitize_text_field( wp_unslash( $atts['label_attributes'] ) ) : __( 'Attributes', 'al-woo-ele-filter' );
        $label_reset      = $atts['label_reset']      !== '' ? sanitize_text_field( wp_unslash( $atts['label_reset'] ) )      : __( 'Reset', 'al-woo-ele-filter' );
        $empty_msg        = __( 'Nincs lehetőség a jelenlegi kiválasztáshoz.', 'al-woo-ele-filter' );

        // Source handling
        $source_attr       = strtolower( sanitize_key( $atts['source'] ) );
        if ( ! in_array( $source_attr, array( 'all', 'sale' ), true ) ) $source_attr = 'all';
        $effective_source  = alpf_get_effective_source( $ns );
        $resolved_source   = ( $source_attr !== 'all' ) ? $source_attr : $effective_source;

        ob_start(); ?>
        <div class="al-pf-wrap"
             data-pf
             id="<?php echo esc_attr( $instance_id ); ?>"
             data-target="<?php echo esc_attr( $target_attr ); ?>"
             data-expose="<?php echo esc_attr( implode( ',', $attr_tax_slugs ) ); ?>"
             data-nonce="<?php echo esc_attr( $nonce ); ?>"
             data-ctx-tax="<?php echo esc_attr( $ctx_tax ); ?>"
             data-ctx-terms="<?php echo esc_attr( implode( ',', $ctx_terms ) ); ?>"
            <?php if ( $ns ) : ?> data-ns="<?php echo esc_attr( $ns ); ?>"<?php endif; ?>>

            <form class="al-pf-form" onsubmit="return false;" role="search" aria-label="<?php echo esc_attr__( 'Product filters', 'al-woo-ele-filter' ); ?>">
                <?php if ( $search_term ) : ?>
                    <input type="hidden" data-param="s" value="<?php echo esc_attr( $search_term ); ?>">
                <?php endif; ?>
                <?php if ( $sel_brand_tax ) : ?>
                    <input type="hidden" data-param="pf_brand_tax" value="<?php echo esc_attr( $sel_brand_tax ); ?>">
                <?php endif; ?>
                <?php if ( $resolved_source === 'sale' ) : ?>
                    <input type="hidden" data-param="pf_source" value="sale">
                <?php endif; ?>

                <!-- Category -->
                <details class="al-pf-dd" data-group="pf_cat" role="listbox" aria-multiselectable="true" id="<?php echo esc_attr($cats_dd_id); ?>">
                    <summary class="al-pf-trigger" aria-label="<?php echo esc_attr( $label_category ); ?>" aria-expanded="false" aria-controls="<?php echo esc_attr($cats_dd_id); ?>-panel">
                        <span class="al-pf-labeltxt"><?php echo esc_html( $label_category ); ?></span>
                        <em class="al-pf-summary" data-summary="pf_cat"></em>
                    </summary>
                    <div class="al-pf-panel" id="<?php echo esc_attr($cats_dd_id); ?>-panel" role="group" aria-label="<?php echo esc_attr( $label_category ); ?>">
                        <div class="al-pf-empty" data-empty="pf_cat" <?php echo empty($present['cats']) ? '' : 'hidden aria-hidden="true"'; ?>>
                            <?php echo esc_html( $empty_msg ); ?>
                        </div>
                        <div class="al-pf-menu" <?php echo empty($present['cats']) ? 'hidden aria-hidden="true"' : ''; ?>>
                            <?php if ( ! empty( $present['cats'] ) ) : foreach ( $present['cats'] as $t ) :
                                $slug = $t->slug; $checked = in_array( $slug, $sel_cats, true ); ?>
                                <label class="al-pf-option">
                                    <input type="checkbox" value="<?php echo esc_attr( $slug ); ?>" <?php checked( $checked ); ?> data-param="pf_cat">
                                    <span><?php echo esc_html( $t->name ); ?></span>
                                </label>
                            <?php endforeach; endif; ?>
                        </div>
                    </div>
                </details>

                <!-- Brand -->
                <?php if ( $sel_brand_tax ) : ?>
                    <details class="al-pf-dd" data-group="pf_brand" role="listbox" aria-multiselectable="true" id="<?php echo esc_attr($brand_dd_id); ?>">
                        <summary class="al-pf-trigger" aria-label="<?php echo esc_attr( $label_brand ); ?>" aria-expanded="false" aria-controls="<?php echo esc_attr($brand_dd_id); ?>-panel">
                            <span class="al-pf-labeltxt"><?php echo esc_html( $label_brand ); ?></span>
                            <em class="al-pf-summary" data-summary="pf_brand"></em>
                        </summary>
                        <div class="al-pf-panel" id="<?php echo esc_attr($brand_dd_id); ?>-panel" role="group" aria-label="<?php echo esc_attr( $label_brand ); ?>">
                            <div class="al-pf-empty" data-empty="pf_brand" <?php echo empty($present['brands']) ? '' : 'hidden aria-hidden="true"'; ?>>
                                <?php echo esc_html( $empty_msg ); ?>
                            </div>
                            <div class="al-pf-menu" <?php echo empty($present['brands']) ? 'hidden aria-hidden="true"' : ''; ?>>
                                <?php if ( ! empty( $present['brands'] ) ) : foreach ( $present['brands'] as $b ) :
                                    $slug    = $b->slug;
                                    $checked = in_array( $slug, $sel_brands, true );
                                    $logo    = al_pf_get_term_logo_url( $b->term_id );
                                    ?>
                                    <label class="al-pf-option al-pf-brand-option">
                                        <input type="checkbox" value="<?php echo esc_attr( $slug ); ?>" <?php checked( $checked ); ?> data-param="pf_brand">
                                        <?php if ( $logo ) : ?>
                                            <img class="al-pf-brand-thumb" src="<?php echo esc_url( $logo ); ?>" alt="" loading="lazy" decoding="async">
                                        <?php endif; ?>
                                        <span><?php echo esc_html( $b->name ); ?></span>
                                    </label>
                                <?php endforeach; endif; ?>
                            </div>
                        </div>
                    </details>
                <?php endif; ?>

                <!-- Attributes -->
                <details class="al-pf-dd" data-group="pf_attr_all" role="listbox" aria-multiselectable="true" id="<?php echo esc_attr($attrs_dd_id); ?>">
                    <summary class="al-pf-trigger" aria-label="<?php echo esc_attr( $label_attributes ); ?>" aria-expanded="false" aria-controls="<?php echo esc_attr($attrs_dd_id); ?>-panel">
                        <span class="al-pf-labeltxt"><?php echo esc_html( $label_attributes ); ?></span>
                        <em class="al-pf-summary" data-summary="pf_attr_all"></em>
                    </summary>
                    <div class="al-pf-panel" id="<?php echo esc_attr($attrs_dd_id); ?>-panel" role="group" aria-label="<?php echo esc_attr( $label_attributes ); ?>">
                        <div class="al-pf-empty" data-empty="pf_attr_all" <?php echo ($attr_total_terms===0) ? '' : 'hidden aria-hidden="true"'; ?>>
                            <?php echo esc_html( $empty_msg ); ?>
                        </div>
                        <div class="al-pf-attr-groups" <?php echo ($attr_total_terms===0) ? 'hidden aria-hidden="true"' : ''; ?>>
                            <?php foreach ( $present['attrs'] as $tax => $terms ) :
                                if ( empty( $terms ) ) continue; ?>
                                <section class="al-pf-attr-group" data-attr-tax="<?php echo esc_attr( $tax ); ?>">
                                    <h4 class="al-pf-group-title"><?php echo esc_html( function_exists('wc_attribute_label') ? wc_attribute_label( $tax ) : $tax ); ?></h4>
                                    <div class="al-pf-grid">
                                        <?php foreach ( $terms as $term ) :
                                            $val = $tax . ':' . $term->slug;
                                            $checked = in_array( $val, $sel_attrs, true ); ?>
                                            <label class="al-pf-option">
                                                <input type="checkbox" value="<?php echo esc_attr( $val ); ?>" <?php checked( $checked ); ?> data-param="pf_attr" data-tax="<?php echo esc_attr( $tax ); ?>">
                                                <span><?php echo esc_html( $term->name ); ?></span>
                                            </label>
                                        <?php endforeach; ?>
                                    </div>
                                </section>
                            <?php endforeach; ?>
                        </div>
                    </div>
                </details>

                <?php if ( 'yes' === strtolower( $atts['show_reset'] ) ) : ?>
                    <button type="button" class="al-pf-reset" aria-label="<?php echo esc_attr( $label_reset ); ?>"><?php echo esc_html( $label_reset ); ?></button>
                <?php endif; ?>
            </form>
        </div>

        <style>
            /* Add your CSS if needed */
        </style>

        <script>
            (function(){
                window.__alpfInstances = window.__alpfInstances || [];

                if (!window.CSS || typeof CSS.escape !== 'function') {
                    window.CSS = window.CSS || {};
                    CSS.escape = CSS.escape || function(value){ return String(value).replace(/[^a-zA-Z0-9_\\-]/g, '\\\\$&'); };
                }

                // Simple SR live region for a11y announcements
                var __alpfSR = document.getElementById('alpf-sr');
                if (!__alpfSR) {
                    __alpfSR = document.createElement('div');
                    __alpfSR.id = 'alpf-sr';
                    __alpfSR.setAttribute('aria-live', 'polite');
                    __alpfSR.style.position='absolute'; __alpfSR.style.width='1px'; __alpfSR.style.height='1px';
                    __alpfSR.style.margin='-1px'; __alpfSR.style.border='0'; __alpfSR.style.padding='0';
                    __alpfSR.style.clip='rect(0 0 0 0)'; __alpfSR.style.overflow='hidden';
                    document.body.appendChild(__alpfSR);
                }

                function vw(){ return Math.max(document.documentElement.clientWidth||0, window.innerWidth||0); }
                function vh(){ return Math.max(document.documentElement.clientHeight||0, window.innerHeight||0); }

                document.querySelectorAll('[data-pf]').forEach(function(root){
                    if (root.__alpfInit) return;
                    root.__alpfInit = true;

                    var targetSelector = root.getAttribute('data-target') || '';
                    var exposeAttrsCsv = root.getAttribute('data-expose') || '';
                    var ajaxNonce      = root.getAttribute('data-nonce') || '';
                    var ctxTax         = root.getAttribute('data-ctx-tax') || '';
                    var ctxTermsCsv    = root.getAttribute('data-ctx-terms') || '';
                    var ns             = root.getAttribute('data-ns') || ''; // NAMESPACE
                    var AJAX_URL = '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>';

                    function nsKey(k){ return ns ? (ns + '_' + k) : k; }

                    /* UX helpers */
                    function smoothShow(el){ if(!el) return; el.hidden=false; el.setAttribute('aria-hidden','false'); el.style.opacity='0'; el.style.transform='scale(0.98)'; requestAnimationFrame(function(){ el.style.transition='opacity .18s ease, transform .18s ease'; el.style.opacity='1'; el.style.transform='scale(1)'; }); setTimeout(function(){ el.style.transition=''; el.style.opacity=''; el.style.transform=''; },220); }
                    function smoothHide(el, after){ if(!el||el.hidden){ if(after) after(); return; } if(el.tagName.toLowerCase()==='details') el.removeAttribute('open'); el.style.transition='opacity .18s ease, transform .18s ease'; el.style.opacity='0'; el.style.transform='scale(0.98)'; setTimeout(function(){ el.hidden=true; el.setAttribute('aria-hidden','true'); el.style.transition=''; el.style.opacity=''; el.style.transform=''; if(after) after(); },200); }
                    function removeWithFade(el){ if(!el) return; smoothHide(el, function(){ if(el&&el.parentNode) el.parentNode.removeChild(el); }); }

                    function getCheckedValues(param, scope){
                        var sel=scope||root;
                        var inputs=sel.querySelectorAll('input[type="checkbox"][data-param="'+param+'"]:checked');
                        var vals=[]; inputs.forEach(function(i){ vals.push(i.value); });
                        return vals;
                    }

                    var applyTimer=null; function scheduleApply(){ clearTimeout(applyTimer); applyTimer=setTimeout(function(){ setSummaryText(); applyAjax(true); }, 160); }

                    function setSummaryText(){
                        function t(n){return n>0?'('+n+')':'';}
                        var a=root.querySelector('[data-summary="pf_cat"]');      if(a){a.textContent=t(getCheckedValues('pf_cat').length);}
                        var b=root.querySelector('[data-summary="pf_brand"]');    if(b){b.textContent=t(getCheckedValues('pf_brand').length);}
                        var c=root.querySelector('[data-summary="pf_attr_all"]'); if(c){c.textContent=t(getCheckedValues('pf_attr').length);}
                    }

                    function applyControlsFromUrl(rootEl, url){
                        var params=(url instanceof URL)?url.searchParams:new URL(url||window.location.href).searchParams;
                        function csv(k){ var v=params.get(nsKey(k))||''; return v? v.split(',').map(function(s){ return s.trim(); }).filter(Boolean):[]; }
                        var cats=csv('pf_cat'), brands=csv('pf_brand'), attrs=csv('pf_attr');
                        rootEl.querySelectorAll('input[type="checkbox"][data-param]').forEach(function(cb){ cb.checked=false; });
                        cats.forEach(function(slug){ var cb=rootEl.querySelector('input[type="checkbox"][data-param="pf_cat"][value="'+CSS.escape(slug)+'"]'); if(cb) cb.checked=true; });
                        brands.forEach(function(slug){ var cb=rootEl.querySelector('input[type="checkbox"][data-param="pf_brand"][value="'+CSS.escape(slug)+'"]'); if(cb) cb.checked=true; });
                        attrs.forEach(function(pair){ var cb=rootEl.querySelector('input[type="checkbox"][data-param="pf_attr"][value="'+CSS.escape(pair)+'"]'); if(cb) cb.checked=true; });
                    }

                    function buildUrl(baseHref, opts){
                        opts = opts || {};
                        var url=new URL(baseHref||window.location.href); var params=url.searchParams;

                        root.querySelectorAll('[data-param="s"], [data-param="pf_brand_tax"], [data-param="pf_source"]').forEach(function(h){
                            var key=h.getAttribute('data-param'), val=h.value||'';
                            val ? params.set(nsKey(key), val) : params.delete(nsKey(key));
                        });
                        var cats=getCheckedValues('pf_cat'); cats.length?params.set(nsKey('pf_cat'),cats.join(',')):params.delete(nsKey('pf_cat'));
                        var br=getCheckedValues('pf_brand'); br.length?params.set(nsKey('pf_brand'),br.join(',')):params.delete(nsKey('pf_brand'));
                        var at=getCheckedValues('pf_attr'); at.length?params.set(nsKey('pf_attr'),at.join(',')):params.delete(nsKey('pf_attr'));

                        // Keep orderby if present (namespaced as well)
                        var curr=new URL(window.location.href);
                        var ob = curr.searchParams.get(nsKey('orderby'));
                        if(!params.has(nsKey('orderby')) && ob) params.set(nsKey('orderby'), ob);

                        if (opts.resetPage) {
                            ['paged','page','product-page'].forEach(function(k){ params.delete(k); });
                        }
                        url.search=params.toString(); return url;
                    }

                    // ===== Robust widget + container detection =====
                    function findTargetPair(doc){
                        var d = doc || document;
                        var wrapper = null, container = null;

                        var widgetSelectors = [
                            '.elementor-widget-loop-grid',
                            '.elementor-widget-wc-archive-products',
                            '[data-widget_type*="loop-grid"]',
                            '[data-element_type="widget"][class*="loop-grid"]'
                        ];
                        var containerSelectors = [
                            '.elementor-loop-container',
                            '.e-loop-container',
                            'ul.products',
                            '.woocommerce ul.products',
                            '.elementor-widget-wc-archive-products .products',
                            '.woocommerce-products',
                            '.products',
                            '.woocommerce .products'
                        ];

                        function first(scope, sels){
                            for (var i=0;i<sels.length;i++){
                                var m = scope.querySelector ? scope.querySelector(sels[i]) : null;
                                if (m) return m;
                            }
                            return null;
                        }

                        if (targetSelector) {
                            var t = d.querySelector(targetSelector);
                            if (t) {
                                if (t.matches && (t.matches(widgetSelectors.join(',')))) {
                                    wrapper = t;
                                } else {
                                    wrapper = first(t, widgetSelectors) || null;
                                }
                                if (wrapper) {
                                    container = first(wrapper, containerSelectors) || wrapper.querySelector('ul.products');
                                } else {
                                    container = first(t, containerSelectors);
                                }
                            }
                        }

                        if (!wrapper) wrapper = first(d, widgetSelectors);
                        if (!container) {
                            if (wrapper) container = first(wrapper, containerSelectors);
                            if (!container) container = first(d, containerSelectors);
                        }
                        return { wrapper: wrapper || null, container: container || null };
                    }

                    function setBusy(hostEl, busy){
                        if (!hostEl) return;
                        if (!hostEl.classList.contains('alpf-overlay-host')) {
                            var cs = getComputedStyle(hostEl);
                            if (cs.position === 'static') hostEl.classList.add('alpf-overlay-host');
                        }
                        if (busy) {
                            if (!hostEl.classList.contains('alpf-target-busy')) {
                                hostEl.classList.add('alpf-target-busy');
                                hostEl.setAttribute('aria-busy','true');
                                var overlay = document.createElement('div');
                                overlay.className = 'alpf-preloader';
                                overlay.setAttribute('role','status');
                                overlay.setAttribute('aria-live','polite');
                                var label = '<?php echo esc_js( __( 'Loading products…', 'al-woo-ele-filter' ) ); ?>';
                                overlay.innerHTML = '<div class="alpf-preloader-inner"><div class="alpf-loader" aria-hidden="true"></div><div class="alpf-label">'+ label +'</div></div>';
                                hostEl.appendChild(overlay);
                                requestAnimationFrame(function(){ overlay.classList.add('alpf-show'); });
                            }
                        } else {
                            hostEl.classList.remove('alpf-target-busy');
                            hostEl.removeAttribute('aria-busy');
                            var overlay = hostEl.querySelector('.alpf-preloader');
                            if (overlay) { overlay.classList.remove('alpf-show'); setTimeout(function(){ overlay && overlay.remove(); }, 180); }
                        }
                    }

                    function rehydrateScripts(scope){
                        if(!scope) return;
                        var scripts = scope.querySelectorAll('script');
                        scripts.forEach(function(oldS){
                            var s = document.createElement('script');
                            for (var i=0;i<oldS.attributes.length;i++) { var a = oldS.attributes[i]; s.setAttribute(a.name, a.value); }
                            s.textContent = oldS.textContent || '';
                            oldS.parentNode.replaceChild(s, oldS);
                        });
                    }
                    function rehydrateElementor(scope){
                        if (window.elementorFrontend) {
                            try {
                                var $scope = window.jQuery ? window.jQuery(scope) : null;
                                if (elementorFrontend.elementsHandler && elementorFrontend.elementsHandler.runReadyTrigger) elementorFrontend.elementsHandler.runReadyTrigger($scope || scope);
                                if (elementorFrontend.hooks && elementorFrontend.hooks.doAction) {
                                    elementorFrontend.hooks.doAction('frontend/element_ready/global', $scope || scope);
                                    elementorFrontend.hooks.doAction('frontend/element_ready/loop-grid.default', $scope || scope);
                                    elementorFrontend.hooks.doAction('frontend/element_ready/woocommerce-archive-products.default', $scope || scope);
                                }
                                if (elementorFrontend.elements && elementorFrontend.elements.$window) elementorFrontend.elements.$window.trigger('resize');
                            } catch(e){ console.warn('[al-pf] Elementor re-init failed:', e); }
                        }
                    }

                    function setEmptyState(groupKey, hasItems) {
                        var panel = root.querySelector('[data-group="'+groupKey+'"] .al-pf-panel');
                        if (!panel) return;
                        var emptyEl = panel.querySelector('.al-pf-empty[data-empty="'+groupKey+'"]');
                        var listEl  = panel.querySelector(groupKey === 'pf_attr_all' ? '.al-pf-attr-groups' : '.al-pf-menu');
                        if (emptyEl) {
                            emptyEl.hidden = !!hasItems;
                            emptyEl.setAttribute('aria-hidden', hasItems ? 'true' : 'false');
                        }
                        if (listEl) {
                            listEl.hidden = !hasItems;
                            listEl.setAttribute('aria-hidden', hasItems ? 'false' : 'true');
                        }
                    }

                    function rebuildCheckboxList(container, param, items) {
                        var selected = new Set(getCheckedValues(param));
                        container.innerHTML = '';
                        var hasItems = !!(items && items.length);

                        if (!hasItems) {
                            setEmptyState(param, false);
                            return;
                        }

                        items.forEach(function(it){
                            var label = document.createElement('label');
                            label.className = 'al-pf-option' + (param === 'pf_brand' ? ' al-pf-brand-option' : '');

                            var input = document.createElement('input');
                            input.type = 'checkbox';
                            input.setAttribute('data-param', param);
                            input.value = it.slug;
                            if (selected.has(it.slug)) input.checked = true;
                            input.addEventListener('change', scheduleApply);
                            label.appendChild(input);

                            if (param === 'pf_brand' && it.logo) {
                                var img = document.createElement('img');
                                img.className = 'al-pf-brand-thumb';
                                img.src = it.logo;
                                img.alt = '';
                                img.loading = 'lazy';
                                img.decoding = 'async';
                                label.appendChild(img);
                            }

                            var span = document.createElement('span');
                            span.textContent = it.name;
                            label.appendChild(span);

                            container.appendChild(label);
                        });

                        setEmptyState(param, true);
                    }

                    async function refreshFilterOptions(urlObj){
                        var u = new URL(AJAX_URL, window.location.origin);
                        var activeUrl = (urlObj instanceof URL) ? urlObj : new URL(window.location.href);
                        u.search = activeUrl.search;
                        u.searchParams.set('action','alpf_terms');
                        if (exposeAttrsCsv) u.searchParams.set('expose_attrs', exposeAttrsCsv);
                        if (ajaxNonce) u.searchParams.set('security', ajaxNonce);
                        if (ctxTax) u.searchParams.set('ctx_tax', ctxTax);
                        if (ctxTermsCsv) u.searchParams.set('ctx_terms', ctxTermsCsv);
                        if (ns) u.searchParams.set('ns', ns); // pass namespace to server

                        try{
                            var res = await fetch(u.toString(), { credentials:'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } });
                            if (!res.ok) throw new Error('HTTP '+res.status);
                            var j = await res.json();
                            if (!j || !j.success) return;

                            var data = j.data || {};
                            var attrLabels = data.attr_labels || {};

                            var catDetails = root.querySelector('[data-group="pf_cat"]');
                            var catMenu    = root.querySelector('[data-group="pf_cat"] .al-pf-menu');
                            if (catDetails && catMenu) {
                                rebuildCheckboxList(catMenu, 'pf_cat', data.cats || []);
                            }

                            var brandDetails = root.querySelector('[data-group="pf_brand"]');
                            var brandMenu    = root.querySelector('[data-group="pf_brand"] .al-pf-menu');
                            if (brandDetails && brandMenu) {
                                rebuildCheckboxList(brandMenu, 'pf_brand', data.brands || []);
                            }

                            // Remove empty attribute sections; we still keep the dropdown visible with empty message
                            Array.from(root.querySelectorAll('.al-pf-attr-group')).forEach(function(sec){
                                var tax = sec.getAttribute('data-attr-tax') || '';
                                var items = (data.attrs && data.attrs[tax]) ? data.attrs[tax] : [];
                                if (!items.length) removeWithFade(sec);
                            });

                            function ensureAttrSection(tax, labelText){
                                var groupsWrap = root.querySelector('.al-pf-attr-groups');
                                if (!groupsWrap) return null;
                                var section = root.querySelector('.al-pf-attr-group[data-attr-tax="'+CSS.escape(tax)+'"]');
                                if (section) return section;
                                section = document.createElement('section');
                                section.className = 'al-pf-attr-group';
                                section.setAttribute('data-attr-tax', tax);
                                var h4 = document.createElement('h4'); h4.className = 'al-pf-group-title'; h4.textContent = labelText || tax;
                                var grid = document.createElement('div'); grid.className = 'al-pf-grid';
                                section.appendChild(h4); section.appendChild(grid);
                                groupsWrap.appendChild(section);
                                return section;
                            }

                            function rebuildAttrGroup(section, tax, items){
                                var grid = section.querySelector('.al-pf-grid');
                                var selected = new Set(getCheckedValues('pf_attr'));
                                grid.innerHTML = '';
                                if (!items || !items.length) return;
                                items.forEach(function(it){
                                    var val = tax + ':' + it.slug;
                                    var label = document.createElement('label'); label.className='al-pf-option';
                                    var input = document.createElement('input');
                                    input.type='checkbox'; input.setAttribute('data-param','pf_attr'); input.setAttribute('data-tax',tax); input.value = val;
                                    if (selected.has(val)) input.checked = true;
                                    input.addEventListener('change', scheduleApply);
                                    var span = document.createElement('span'); span.textContent = it.name;
                                    label.appendChild(input); label.appendChild(span);
                                    grid.appendChild(label);
                                });
                            }

                            (Object.keys(data.attrs||{})).forEach(function(tax){
                                var items = (data.attrs && data.attrs[tax]) ? data.attrs[tax] : [];
                                if (!items.length) return;
                                var labelText = (attrLabels && attrLabels[tax]) ? attrLabels[tax] : tax;
                                var section = ensureAttrSection(tax, labelText);
                                if (!section) return;
                                rebuildAttrGroup(section, tax, items);
                                section.hidden=false; section.setAttribute('aria-hidden','false');
                                section.style.opacity='0'; section.style.transform='scale(0.98)';
                                requestAnimationFrame(function(){
                                    section.style.transition='opacity .18s ease, transform .18s ease';
                                    section.style.opacity='1'; section.style.transform='scale(1)';
                                    setTimeout(function(){ section.style.transition=''; section.style.opacity=''; section.style.transform=''; },220);
                                });
                            });

                            var anyAttrItems = data.attrs && Object.keys(data.attrs).some(function(t){ return (data.attrs[t]||[]).length; });
                            setEmptyState('pf_attr_all', !!anyAttrItems);

                            setSummaryText();
                        } catch(err){ console.warn('[al-pf] terms refresh failed:', err); }
                    }

                    async function fetchAndSwap(url){
                        var cur = findTargetPair(document);
                        var busyHost = cur.wrapper || cur.container;
                        if (!busyHost) { window.location.href = url.toString(); return; }

                        setBusy(busyHost, true);
                        var safetyTimer = setTimeout(function(){ setBusy(busyHost, false); }, 8000);

                        try {
                            var xhrUrl = new URL(url.toString());
                            xhrUrl.searchParams.set('_alpf', '1');

                            var res = await fetch(xhrUrl.toString(), {
                                credentials:'same-origin',
                                headers: { 'X-Requested-With': 'XMLHttpRequest' }
                            });
                            if (!res.ok) throw new Error('HTTP '+res.status);
                            var html = await res.text();
                            var parser = new DOMParser();
                            var doc = parser.parseFromString(html, 'text/html');

                            var neu = findTargetPair(doc);
                            var newWrapper = neu.wrapper;
                            var newContainer = neu.container;

                            if (!newWrapper && !newContainer) throw new Error('Target nodes not found in response');

                            var didReplaceWrapper = false;
                            if (cur.wrapper && newWrapper) {
                                var tmp = document.createElement('div');
                                tmp.innerHTML = newWrapper.outerHTML;
                                var fresh = tmp.firstElementChild;
                                cur.wrapper.replaceWith(fresh);
                                var updated = findTargetPair(document);
                                didReplaceWrapper = true;
                                rehydrateScripts(updated.wrapper || updated.container);
                                rehydrateElementor(updated.wrapper || updated.container);
                                bindContainerInteractions(updated.container || updated.wrapper);
                                setBusy(updated.wrapper || updated.container, false);
                            } else if (cur.container && newContainer) {
                                cur.container.innerHTML = newContainer.innerHTML;
                                rehydrateScripts(cur.container);
                                rehydrateElementor(cur.container);
                                bindContainerInteractions(cur.container);
                            } else {
                                window.location.href = url.toString();
                                return;
                            }

                            refreshFilterOptions(url);

                            var region = (didReplaceWrapper ? (findTargetPair(document).wrapper || findTargetPair(document).container) : cur.container);
                            if (region) {
                                region.setAttribute('role','region');
                                region.setAttribute('aria-live','polite');
                                region.scrollIntoView({ behavior:'smooth', block:'start' });
                            }

                            __alpfSR.textContent = '<?php echo esc_js( __( 'Products updated', 'al-woo-ele-filter' ) ); ?>';

                            document.dispatchEvent(new CustomEvent('alpf:afterSwap', { detail: { container: region, url: url.toString() } }));
                            if (window.jQuery) {
                                jQuery(document.body).trigger('alpf_after_swap', [ region, url.toString() ]);
                                jQuery(document.body).trigger('updated_wc_div');
                                jQuery(document.body).trigger('wc_fragment_refresh');
                                jQuery(document.body).trigger('init_price_filter');
                                jQuery(document.body).trigger('wc_update_cart');
                            }
                        } catch(err){
                            console.error('[al-pf] AJAX swap failed:', err);
                            window.location.href = url.toString();
                        } finally {
                            clearTimeout(safetyTimer);
                            var latest = findTargetPair(document);
                            setBusy(latest.wrapper || latest.container || busyHost, false);
                        }
                    }

                    function applyAjax(filtersChanged){
                        var url = buildUrl(undefined, { resetPage: !!filtersChanged });
                        var href = url.toString();
                        if (href !== window.location.href) {
                            history.pushState({ alpf: 1, href: href }, '', href);
                        }
                        fetchAndSwap(url);
                        if (filtersChanged) closeAll(null);
                    }

                    // Close all dropdowns except the provided one
                    function closeAll(except){
                        root.querySelectorAll('.al-pf-dd[open]').forEach(function(dd){ if (dd !== except) dd.removeAttribute('open'); });
                    }

                    // ===== Mobile viewport clamping / gap fix (no overlay) =====
                    function fitPanel(dd){
                        if (!dd) return;
                        var panel = dd.querySelector('.al-pf-panel'); if (!panel) return;

                        // Reset positioning
                        panel.classList.remove('mobile-fit');
                        panel.style.position=''; panel.style.left=''; panel.style.right=''; panel.style.top=''; panel.style.bottom='';
                        panel.style.maxHeight=''; panel.style.width=''; panel.style.maxWidth=''; panel.classList.remove('fit-clamped');
                        dd.classList.remove('flip');

                        var viewportW = vw();
                        var viewportH = vh();
                        var GAP = 10; // ensure there's always a visual gap from the opener

                        // Small screens: fixed sheet without overlay; clamp and add gap so it never overlaps opener
                        if (viewportW <= 480) {
                            var ddRect = dd.getBoundingClientRect();
                            panel.classList.add('mobile-fit');
                            panel.style.left = '8px';
                            panel.style.right = '8px';
                            panel.style.maxHeight = Math.round(viewportH * 0.7) + 'px';

                            // Measure current height (after maxHeight applied)
                            var ph = panel.getBoundingClientRect().height || Math.round(viewportH * 0.7);

                            var spaceBelow = viewportH - ddRect.bottom - GAP;
                            var spaceAbove = ddRect.top - GAP;

                            if (spaceBelow >= Math.min(ph, viewportH * 0.4)) {
                                // Open below, with gap -> no overlap with opener
                                var top = Math.min(ddRect.bottom + GAP, viewportH - GAP - Math.min(ph, Math.round(viewportH * 0.7)));
                                panel.style.top = top + 'px';
                            } else {
                                // Open above, with gap
                                var topAbove = Math.max(GAP, ddRect.top - GAP - Math.min(ph, Math.round(viewportH * 0.7)));
                                panel.style.top = topAbove + 'px';
                            }
                            return;
                        }

                        // Desktop/tablet: absolute positioning with clamping and flip
                        var vwPadding = 12;
                        var maxW = Math.min(1200, viewportW - (vwPadding*2));
                        var natural = Math.ceil(panel.scrollWidth + 20);
                        var minW = Math.max(220, Math.min(340, viewportW - 2*vwPadding));
                        var finalW = Math.max(minW, Math.min(natural, maxW));
                        panel.style.maxWidth = Math.round(maxW) + 'px';
                        panel.style.width = Math.round(finalW) + 'px';

                        panel.style.left='0px'; panel.style.right='auto';
                        var rect=panel.getBoundingClientRect();
                        if (rect.right > viewportW - vwPadding) { panel.style.left='auto'; panel.style.right='0px'; }

                        var ddRect=dd.getBoundingClientRect(); var spaceBelow=viewportH-ddRect.bottom; var spaceAbove=ddRect.top;
                        if (spaceBelow < rect.height && spaceAbove > spaceBelow) dd.classList.add('flip');

                        // After flip, re-measure and correct horizontal overflow
                        rect = panel.getBoundingClientRect();
                        if (rect.left < vwPadding) {
                            panel.style.left = (vwPadding - (ddRect.left)) + 'px';
                            panel.style.right = 'auto';
                        }
                        rect = panel.getBoundingClientRect();
                        if (rect.right > viewportW - vwPadding) {
                            panel.style.left = 'auto';
                            panel.style.right = (vwPadding) + 'px';
                        }
                    }

                    function bindContainerInteractions(container){
                        if (!container) return;
                        if (!container.dataset.alpfPagBound) {
                            container.addEventListener('click', function(e){
                                var a = e.target.closest('.page-numbers a, a.page-numbers, .woocommerce-pagination a');
                                if (!a) return;
                                e.preventDefault();
                                var url = new URL(a.href);
                                var href = url.toString();
                                if (href !== window.location.href) {
                                    history.pushState({ alpf:1, href: href }, '', href);
                                }
                                fetchAndSwap(url);
                            }, { passive:false });
                            container.dataset.alpfPagBound = '1';
                        }

                        var orderForm = container.querySelector('form.woocommerce-ordering');
                        var select    = orderForm ? orderForm.querySelector('select[name="orderby"]') : null;
                        if (!select) {
                            var globalForm = document.querySelector('form.woocommerce-ordering');
                            if (globalForm) select = globalForm.querySelector('select[name="orderby"]');
                        }
                        if (select && !select.dataset.alpfBound) {
                            select.addEventListener('change', function(){
                                var url = buildUrl(undefined, { resetPage: true });
                                url.searchParams.set(nsKey('orderby'), select.value); // namespaced orderby
                                var href = url.toString();
                                if (href !== window.location.href) {
                                    history.pushState({ alpf:1, href: href }, '', href);
                                }
                                fetchAndSwap(url);
                            });
                            select.dataset.alpfBound = '1';
                        }
                    }

                    // Toggle behavior + events (enforce single-open)
                    root.querySelectorAll('.al-pf-dd').forEach(function(dd){
                        var summary = dd.querySelector('.al-pf-trigger');

                        // Pre-close siblings BEFORE the browser toggles (pointer/keyboard)
                        if (summary) {
                            summary.addEventListener('pointerdown', function(){
                                if (!dd.open) closeAll(dd);
                            });
                            summary.addEventListener('keydown', function(e){
                                if (e.key === 'Enter' || e.key === ' ') {
                                    if (!dd.open) closeAll(dd);
                                }
                            });
                        }

                        // Fallback when toggle completes
                        dd.addEventListener('toggle', function(){
                            if (summary) summary.setAttribute('aria-expanded', dd.open ? 'true' : 'false');
                            if (dd.open) {
                                closeAll(dd);
                                fitPanel(dd);
                            }
                        });

                        // Close on ESC
                        dd.addEventListener('keydown', function(e){
                            if (e.key === 'Escape') dd.removeAttribute('open');
                        });
                    });

                    // Refit on resize/orientation/scroll (helps fixed mobile sheet stay aligned)
                    var refit = function(){
                        var openDD = root.querySelector('.al-pf-dd[open]');
                        if (openDD) fitPanel(openDD);
                    };
                    window.addEventListener('resize', refit, { passive:true });
                    window.addEventListener('orientationchange', refit, { passive:true });
                    window.addEventListener('scroll', function(){
                        if (document.querySelector('.al-pf-panel.mobile-fit')) refit();
                    }, { passive:true });

                    document.addEventListener('click', function(e){
                        var isInside = e.target.closest ? e.target.closest('.al-pf-dd') : null;
                        if (isInside && root.contains(isInside)) return;
                        if (!root.contains(e.target)) closeAll(null);
                    });

                    // Change listeners
                    root.querySelectorAll('input[type="checkbox"][data-param]').forEach(function(cb){ cb.addEventListener('change', scheduleApply); });

                    // Reset
                    var resetBtn = root.querySelector('.al-pf-reset');
                    if (resetBtn){
                        resetBtn.addEventListener('click', function(){
                            root.querySelectorAll('input[type="checkbox"][data-param]').forEach(function(cb){ cb.checked = false; });
                            setSummaryText();
                            var url = buildUrl(undefined, { resetPage: true });
                            url.searchParams.delete(nsKey('orderby'));
                            var href = url.toString();
                            if (href !== window.location.href) {
                                history.pushState({ alpf:1, href: href }, '', href);
                            }
                            fetchAndSwap(url);
                            closeAll(null);
                        });
                    }

                    // Initial
                    applyControlsFromUrl(root, window.location.href);
                    var initialPair = findTargetPair(document);
                    bindContainerInteractions(initialPair.container || initialPair.wrapper);
                    setSummaryText();
                    refreshFilterOptions(new URL(window.location.href));

                    if (initialPair.container || initialPair.wrapper) {
                        var scope = initialPair.wrapper || initialPair.container;
                        document.dispatchEvent(new CustomEvent('alpf:afterSwap', { detail: { container: scope, url: window.location.href } }));
                        if (window.jQuery) jQuery(document.body).trigger('alpf_after_swap', [ scope, window.location.href ]);
                    }

                    window.__alpfInstances.push({ root: root, refresh: function(){ var url = new URL(window.location.href); fetchAndSwap(url); } });
                });

                // History back/forward — honor namespace
                window.addEventListener('popstate', function(){
                    if (!window.__alpfInstances) return;
                    var url = new URL(window.location.href);
                    window.__alpfInstances.forEach(function(i){
                        if (!i || !i.root) return;
                        var root=i.root, ns=root.getAttribute('data-ns')||'';
                        function nsKey(k){ return ns ? (ns+'_'+k) : k; }
                        function csv(k){ var v=url.searchParams.get(nsKey(k))||''; return v? v.split(',').map(function(s){return s.trim();}).filter(Boolean):[]; }
                        var cats=csv('pf_cat'), brands=csv('pf_brand'), attrs=csv('pf_attr');
                        root.querySelectorAll('input[type="checkbox"][data-param]').forEach(function(cb){ cb.checked=false; });
                        cats.forEach(function(slug){ var cb=root.querySelector('input[type="checkbox"][data-param="pf_cat"][value="'+CSS.escape(slug)+'"]'); if(cb) cb.checked=true; });
                        brands.forEach(function(slug){ var cb=root.querySelector('input[type="checkbox"][data-param="pf_brand"][value="'+CSS.escape(slug)+'"]'); if(cb) cb.checked=true; });
                        attrs.forEach(function(pair){ var cb=root.querySelector('input[type="checkbox"][data-param="pf_attr"][value="'+CSS.escape(pair)+'"]'); if(cb) cb.checked=true; });
                        function gv(p){ return Array.from(root.querySelectorAll('input[type="checkbox"][data-param="'+p+'"]:checked')).map(function(n){return n.value;}); }
                        function t(n){return n>0?'('+n+')':'';}
                        var a=root.querySelector('[data-summary="pf_cat"]');      if(a){a.textContent=t(gv('pf_cat').length);}
                        var b=root.querySelector('[data-summary="pf_brand"]');    if(b){b.textContent=t(gv('pf_brand').length);}
                        var c=root.querySelector('[data-summary="pf_attr_all"]'); if(c){c.textContent=t(gv('pf_attr').length);}
                        if (typeof i.refresh === 'function') i.refresh();
                    });
                });
            })();
        </script>
        <?php
        return ob_get_clean();
    }
}
add_shortcode( 'wc_filter_bar', 'al_pf_filter_bar_shortcode' );

/** =========================
 *  Server-side query filters (main query)
 *  ========================= */
if ( ! function_exists( 'al_pf_filter_main_search_query' ) ) {
    function al_pf_filter_main_search_query( $q ) {
        if ( is_admin() || ! ( $q instanceof WP_Query ) || ! $q->is_main_query() ) return;

        // Detect any namespaced or non-namespaced PF params
        $has_pf_params = alpf_request_has_pf_params_globally();

        $is_product_archive = function_exists('is_post_type_archive') && is_post_type_archive('product');
        $is_product_tax     = false;
        if ( function_exists('get_object_taxonomies') && ! is_null( get_object_taxonomies( 'product' ) ) ) {
            foreach ( get_object_taxonomies( 'product' ) as $ptax ) {
                if ( function_exists('is_tax') && is_tax( $ptax ) ) { $is_product_tax = true; break; }
            }
        }
        $is_product_pt = ( 'product' === $q->get('post_type') || ( is_array( $q->get('post_type') ) && in_array( 'product', (array) $q->get('post_type'), true ) ) );

        if ( $q->is_search() && ! $has_pf_params && ! $is_product_archive && ! $is_product_tax && ! $is_product_pt ) return;

        if ( $q->is_search() && $has_pf_params ) {
            $q->set( 'post_type', array( 'product' ) );
        }

        if ( $has_pf_params || $is_product_archive || $is_product_tax || $is_product_pt ) {
            $parts = al_pf_build_tax_query_parts( '', '' );
            if ( $parts ) {
                $tax_query = (array) $q->get( 'tax_query' );
                $tax_query = array_merge( $tax_query, $parts );
                if ( count( $tax_query ) > 1 && ! isset( $tax_query['relation'] ) ) {
                    $tax_query = array_merge( array( 'relation' => 'AND' ), $tax_query );
                }
                $q->set( 'tax_query', $tax_query );
            }

            list( $guard_tax, $guard_meta ) = alpf_build_catalog_guards( $q->is_search(), '' );
            $tax_query  = array_merge( (array) $q->get('tax_query'),  $guard_tax );
            $meta_query = array_merge( (array) $q->get('meta_query'), $guard_meta );

            if ( count( $tax_query ) > 1 && ! isset( $tax_query['relation'] ) ) {
                $tax_query = array_merge( array( 'relation' => 'AND' ), $tax_query );
            }
            if ( count( $meta_query ) > 1 && ! isset( $meta_query['relation'] ) ) {
                $meta_query = array_merge( array( 'relation' => 'AND' ), $meta_query );
            }

            // Apply effective source globally (ns '')
            $args = al_pf_apply_source_to_args( array(), '' );
            if ( ! empty( $args['post__in'] ) ) {
                $existing = (array) $q->get( 'post__in' );
                if ( ! empty( $existing ) ) {
                    $q->set( 'post__in', array_values( array_intersect( array_map( 'intval', $existing ), array_map( 'intval', $args['post__in'] ) ) ) );
                } else {
                    $q->set( 'post__in', $args['post__in'] );
                }
            }

            $q->set( 'tax_query',  $tax_query );
            $q->set( 'meta_query', $meta_query );
        }
    }
}
add_action( 'pre_get_posts', 'al_pf_filter_main_search_query' );

/** =========================
 *  Elementor Loop Grid query — namespaced
 *  ========================= */
if ( ! function_exists( 'al_pf_elementor_posts_query' ) ) {
    function al_pf_elementor_posts_query( $query ) {
        if ( is_admin() ) return;
        if ( ! ( $query instanceof WP_Query ) ) return;

        $hook = current_filter();
        $parts = explode( '/', (string) $hook );
        $ns = sanitize_key( end( $parts ) );

        $default_pp = (int) apply_filters( 'alpf_posts_per_page_default', 30 );
        $pp = (int) $query->get( 'posts_per_page' );
        if ( $pp < 1 ) $query->set( 'posts_per_page', $default_pp );

        // namespaced orderby
        $orderby_raw = alpf_get_param( 'orderby', $ns );
        if ( $orderby_raw !== null && $orderby_raw !== '' ) {
            $orderby = function_exists('wc_clean') ? wc_clean( wp_unslash( $orderby_raw ) ) : sanitize_text_field( wp_unslash( $orderby_raw ) );
            al_pf_apply_elementor_orderby_map( $query, $orderby );
        }

        // Any namespaced filter/search or effective source default?
        $effective_source = alpf_get_effective_source( $ns );
        $has_params = (
            alpf_get_param( 'pf_cat', $ns ) !== null ||
            alpf_get_param( 'pf_brand', $ns ) !== null ||
            alpf_get_param( 'pf_attr', $ns ) !== null ||
            alpf_get_param( 'pf_brand_tax', $ns ) !== null ||
            alpf_get_param( 's', $ns ) !== null ||
            alpf_get_param( 'min_price', $ns ) !== null ||
            alpf_get_param( 'max_price', $ns ) !== null ||
            $effective_source !== ''
        );

        if ( ! $has_params ) return;

        $pt = $query->get('post_type');
        if ( $pt && $pt !== 'product' && ( ! is_array($pt) || ! in_array( 'product', (array) $pt, true ) ) ) return;

        $parts = al_pf_build_tax_query_parts( '', $ns );
        if ( $parts ) {
            $tax_query = (array) $query->get( 'tax_query' );
            $tax_query = array_merge( $tax_query, $parts );
            if ( count( $tax_query ) > 1 && ! isset( $tax_query['relation'] ) ) {
                $tax_query = array_merge( array( 'relation' => 'AND' ), $tax_query );
            }
            $query->set( 'tax_query', $tax_query );
        }

        $s_raw = alpf_get_param( 's', $ns );
        if ( $s_raw !== null && $s_raw !== '' ) {
            $query->set( 'post_type', array( 'product' ) );
            $query->set( 's', sanitize_text_field( wp_unslash( $s_raw ) ) );
        }

        list( $guard_tax, $guard_meta ) = alpf_build_catalog_guards( $s_raw !== null && $s_raw !== '', $ns );
        $tax_query  = array_merge( (array) $query->get('tax_query'),  $guard_tax );
        $meta_query = array_merge( (array) $query->get('meta_query'), $guard_meta );

        if ( count( $tax_query ) > 1 && ! isset( $tax_query['relation'] ) ) {
            $tax_query = array_merge( array( 'relation' => 'AND' ), $tax_query );
        }
        if ( count( $meta_query ) > 1 && ! isset( $meta_query['relation'] ) ) {
            $meta_query = array_merge( array( 'relation' => 'AND' ), $meta_query );
        }

        // Apply effective source (e.g., sale) for this namespace, intersect with existing post__in
        $args = al_pf_apply_source_to_args( array(), $ns );
        if ( ! empty( $args['post__in'] ) ) {
            $existing = (array) $query->get( 'post__in' );
            if ( ! empty( $existing ) ) {
                $query->set( 'post__in', array_values( array_intersect( array_map( 'intval', $existing ), array_map( 'intval', $args['post__in'] ) ) ) );
            } else {
                $query->set( 'post__in', $args['post__in'] );
            }
        }

        $query->set( 'tax_query',  $tax_query );
        $query->set( 'meta_query', $meta_query );
    }
}

/** Woo 'orderby' map */
if ( ! function_exists( 'al_pf_apply_elementor_orderby_map' ) ) {
    function al_pf_apply_elementor_orderby_map( WP_Query $query, $orderby ) {
        switch ( $orderby ) {
            case 'price':
                $query->set( 'meta_key', '_price' ); $query->set( 'orderby', 'meta_value_num' ); $query->set( 'order', 'ASC' ); break;
            case 'price-desc':
                $query->set( 'meta_key', '_price' ); $query->set( 'orderby', 'meta_value_num' ); $query->set( 'order', 'DESC' ); break;
            case 'popularity':
                $query->set( 'meta_key', 'total_sales' ); $query->set( 'orderby', 'meta_value_num' ); $query->set( 'order', 'DESC' ); break;
            case 'rating':
                $query->set( 'meta_key', '_wc_average_rating' ); $query->set( 'orderby', 'meta_value_num' ); $query->set( 'order', 'DESC' ); break;
            case 'date':
                $query->set( 'orderby', 'date' ); $query->set( 'order', 'DESC' ); break;
            case 'menu_order':
            case 'default':
            default:
                $query->set( 'orderby', 'menu_order title' ); $query->set( 'order', 'ASC' ); break;
        }
    }
}

/** =========================
 *  AJAX: terms (namespaced)
 *  ========================= */
if ( ! function_exists( 'al_pf_ajax_terms' ) ) {
    function al_pf_ajax_terms() {
        if ( empty( $_REQUEST['security'] ) || ! wp_verify_nonce( $_REQUEST['security'], 'alpf_terms' ) ) {
            wp_send_json_error( array( 'message' => 'Invalid nonce' ), 403 );
        }

        $ns = ! empty( $_REQUEST['ns'] ) ? sanitize_key( wp_unslash( $_REQUEST['ns'] ) ) : '';

        $brand_tax = '';
        $brand_tax_req = alpf_get_param( 'pf_brand_tax', $ns );
        if ( ! empty( $brand_tax_req ) ) {
            $brand_tax = sanitize_key( wp_unslash( $brand_tax_req ) );
            if ( ! taxonomy_exists( $brand_tax ) ) $brand_tax = '';
        }
        if ( ! $brand_tax ) $brand_tax = al_pf_get_brand_taxonomy();

        $attr_tax_slugs = array();
        if ( ! empty( $_REQUEST['expose_attrs'] ) ) {
            foreach ( explode( ',', (string) wp_unslash( $_REQUEST['expose_attrs'] ) ) as $maybe ) {
                $tax = sanitize_key( trim( $maybe ) );
                if ( $tax && taxonomy_exists( $tax ) ) $attr_tax_slugs[] = $tax;
            }
        } else if ( function_exists( 'wc_get_attribute_taxonomies' ) ) {
            foreach ( wc_get_attribute_taxonomies() as $ga ) {
                $tax = 'pa_' . $ga->attribute_name;
                if ( taxonomy_exists( $tax ) ) $attr_tax_slugs[] = $tax;
            }
        }

        $ctx_tax   = ! empty( $_REQUEST['ctx_tax'] ) ? sanitize_key( wp_unslash( $_REQUEST['ctx_tax'] ) ) : '';
        $ctx_terms = array();
        if ( ! empty( $_REQUEST['ctx_terms'] ) ) {
            $ctx_terms = array_filter( array_map( 'sanitize_title', array_map( 'trim', explode( ',', (string) wp_unslash( $_REQUEST['ctx_terms'] ) ) ) ) );
        }

        $terms = al_pf_collect_terms_for_filters( $brand_tax, $attr_tax_slugs, array(
            'tax'   => $ctx_tax,
            'terms' => $ctx_terms,
        ), $ns );

        $out = array(
            'cats'        => array(),
            'brands'      => array(),
            'attrs'       => array(),
            'attr_labels' => array(),
            'brand_tax'   => $brand_tax,
        );

        foreach ( (array) $terms['cats'] as $t ) {
            $out['cats'][]   = array( 'slug'=>$t->slug, 'name'=>$t->name );
        }
        foreach ( (array) $terms['brands'] as $b ) {
            $out['brands'][] = array(
                'slug' => $b->slug,
                'name' => $b->name,
                'logo' => al_pf_get_term_logo_url( $b->term_id ),
            );
        }
        foreach ( (array) $terms['attrs'] as $tax => $arr ) {
            $group = array();
            foreach ( (array) $arr as $term ) {
                $group[] = array( 'slug'=>$term->slug, 'name'=>$term->name, 'tax'=>$tax );
            }
            $out['attrs'][ $tax ] = $group;
        }

        $all_attr_taxes = $attr_tax_slugs ? $attr_tax_slugs : array_keys( (array) $terms['attrs'] );
        foreach ( $all_attr_taxes as $tax ) {
            $out['attr_labels'][ $tax ] = function_exists('wc_attribute_label') ? wc_attribute_label( $tax ) : $tax;
        }

        nocache_headers();
        wp_send_json_success( $out );
    }
}
add_action( 'wp_ajax_nopriv_alpf_terms', 'al_pf_ajax_terms' );
add_action( 'wp_ajax_alpf_terms',        'al_pf_ajax_terms' );

/** =========================
 *  Plugin defaults / developer hooks
 *  =========================
 * Ensure first-paint sale filtering for the common namespace "sale_query_id".
 */
add_filter( 'alpf_default_source_map', function( $map ){
    if ( ! is_array( $map ) ) $map = array();
    if ( empty( $map['sale_query_id'] ) ) {
        $map['sale_query_id'] = 'sale';
    }
    return $map;
}, 5 );

/** Cache key extra — language/currency awareness (optional but helpful) */
add_filter( 'alpf_transient_key_extra', function( $extra ) {
    $lang = defined( 'ICL_LANGUAGE_CODE' ) ? ICL_LANGUAGE_CODE : ( function_exists( 'pll_current_language' ) ? pll_current_language() : '' );
    $currency = function_exists( 'get_woocommerce_currency' ) ? get_woocommerce_currency() : '';
    return trim( $extra . '|' . $lang . '|' . $currency, '|' );
}, 5 );

/** Index + bust term scan caches when catalog changes */
if ( ! function_exists( 'alpf_index_transient' ) ) {
    function alpf_index_transient( $key ) {
        $idx = get_option( 'alpf_terms_keys', array() );
        if ( ! in_array( $key, $idx, true ) ) {
            $idx[] = $key;
            update_option( 'alpf_terms_keys', $idx, false );
        }
    }
}
if ( ! function_exists( 'alpf_clear_terms_cache' ) ) {
    function alpf_clear_terms_cache() {
        $idx = (array) get_option( 'alpf_terms_keys', array() );
        foreach ( $idx as $k ) { delete_transient( $k ); }
        update_option( 'alpf_terms_keys', array(), false );
        delete_transient( 'alpf_sale_ids_v1' );
    }
}
add_action( 'save_post_product', 'alpf_clear_terms_cache' );
add_action( 'created_term', 'alpf_clear_terms_cache', 10, 3 );
add_action( 'edited_term',  'alpf_clear_terms_cache', 10, 3 );
add_action( 'delete_term',  'alpf_clear_terms_cache', 10, 4 );


/**
 * Plugin Name: WooCommerce Qty Plus/Minus Span Controls
 * Description: Replaces the default qty input arrows with – [qty] + spans on product and cart pages. Adds a shortcode to place a synced quantity control anywhere on single product pages. Automatically updates the cart after quantity change.
 * Version:     1.4.2
 * Author:      Your Name
 * License:     GPL2
 */

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

/**
 * Enqueue CSS & JS for qty controls on product and cart pages.
 */
add_action( 'wp_enqueue_scripts', 'wc_qty_span_controls_enqueue_assets' );
function wc_qty_span_controls_enqueue_assets() {
    if ( ! is_product() && ! is_cart() ) return;

    // Register empty handles so we can attach inline styles/scripts
    wp_register_style( 'wc-qty-span-style', false );
    wp_enqueue_style( 'wc-qty-span-style' );
    wp_register_script( 'wc-qty-span-script', false, array( 'jquery' ), '1.4.2', true );
    wp_enqueue_script( 'wc-qty-span-script' );

    // Custom CSS for styling the quantity controls
    $css = <<<CSS
/* Remove spin buttons for both */


CSS;
    wp_add_inline_style( 'wc-qty-span-style', $css );

    // Custom JS for quantity plus/minus, syncing, and auto cart update
    $js = <<<JS
jQuery(function($){
    // Handle clicks on our spans
    $(document).on('click', '.qty-btn', function(){
        var \$btn   = $(this),
            \$wrap  = \$btn.closest('.quantity'),
            \$input = \$wrap.find('input.qty').first(),
            step    = parseFloat( \$input.attr('step') ) || 1,
            min     = parseFloat( \$input.attr('min') )  || 0,
            max     = parseFloat( \$input.attr('max') ),
            val     = parseFloat( \$input.val() )        || 0;

        if ( \$btn.hasClass('qty-inc') ) {
            val += step;
            if ( ! isNaN(max) && val > max ) val = max;
        } else {
            val -= step;
            if ( val < min ) val = min;
        }

        \$input.val(val).trigger('change');
    });

    // Keep all shortcode proxies in sync with the main single-product input
    function getMainQtyInput() {
        return $('form.cart').first().find('input.qty[name="quantity"]').first();
    }

    function syncProxiesFromMain() {
        var \$main = getMainQtyInput();
        if (!\$main.length) return;
        var val = \$main.val();
        $('.wc-qty-proxy').each(function(){
            var \$proxyInput = $(this).find('input.qty').first();
            if (\$proxyInput.length && \$proxyInput.val() !== val) {
                \$proxyInput.val(val);
            }
        });
    }

    function syncMainFromProxy(\$proxyInput) {
        var \$main = getMainQtyInput();
        if (!\$main.length) return;
        var val = \$proxyInput.val();
        if (\$main.val() !== val) {
            \$main.val(val).trigger('change');
        }
    }

    // On proxy change, update main
    $(document).on('change keyup input', '.wc-qty-proxy input.qty', function(){
        syncMainFromProxy($(this));
    });

    // When main changes (e.g., by native control or other scripts), update proxies
    $(document).on('change keyup updated_wc_qty', 'form.cart input.qty[name="quantity"]', function(){
        syncProxiesFromMain();
    });

    // Initial sync on load (handles variations initializing qty later as well)
    var initSync = function(){ syncProxiesFromMain(); };
    $(document).on('found_variation reset_data', initSync);
    $(document).ready(initSync);

    // On cart, auto-submit when qty changes (for classic and Elementor carts)
    $(document).on('change', 'table.cart input.qty', function(){
        var \$form = $(this).closest('form.woocommerce-cart-form');
        var \$updateBtn = \$form.find('button[name="update_cart"]');
        if (\$updateBtn.length) {
            \$updateBtn.prop('disabled', false);
            \$updateBtn.trigger('click');
        }
    });
});
JS;
    wp_add_inline_script( 'wc-qty-span-script', $js );
}

/**
 * Output the “−” span before the qty input.
 */
add_action( 'woocommerce_before_quantity_input_field', 'wc_qty_span_add_dec' );
function wc_qty_span_add_dec() {
    echo '<span class="qty-btn qty-dec" tabindex="0" aria-label="Decrease quantity">&minus;</span>';
}

/**
 * Output the “+” span after the qty input.
 */
add_action( 'woocommerce_after_quantity_input_field', 'wc_qty_span_add_inc' );
function wc_qty_span_add_inc() {
    echo '<span class="qty-btn qty-inc" tabindex="0" aria-label="Increase quantity">+</span>';
}

/**
 * Shortcode: [wc_qty_control]
 * Renders a synced quantity control anywhere on a single product page.
 *
 * Attributes:
 * - product_id (int) Optional; defaults to current product.
 * - min, max, step (numbers) Optional; fall back to product purchase constraints.
 * - value (number) Optional; initial value (defaults to main input / product min).
 * - class (string) Optional; extra CSS class on wrapper.
 *
 * Notes:
 * - Uses woocommerce_quantity_input() so the +/- spans still apply via the hooks above.
 * - Uses a proxy input name to avoid altering POST; JS keeps it in sync with main qty.
 */
add_shortcode( 'wc_qty_control', 'wc_qty_span_shortcode_render' );
function wc_qty_span_shortcode_render( $atts ) {
    if ( ! is_product() ) return ''; // Only makes sense on single product pages

    $atts = shortcode_atts( array(
        'product_id' => 0,
        'min'        => '',
        'max'        => '',
        'step'       => '',
        'value'      => '',
        'class'      => '',
    ), $atts, 'wc_qty_control' );

    // Resolve product safely without colliding with $product global
    $wc_product = null;

    if ( absint( $atts['product_id'] ) > 0 ) {
        $wc_product = wc_get_product( absint( $atts['product_id'] ) );
    }
    if ( ! $wc_product instanceof WC_Product ) {
        global $product; // from the single product loop
        if ( $product instanceof WC_Product ) {
            $wc_product = $product;
        }
    }
    if ( ! $wc_product instanceof WC_Product ) {
        return ''; // no valid product context
    }

    // Determine constraints
    $min  = ( $atts['min']  !== '' ) ? floatval( $atts['min'] )  : $wc_product->get_min_purchase_quantity();
    $max  = ( $atts['max']  !== '' ) ? floatval( $atts['max'] )  : $wc_product->get_max_purchase_quantity();
    $step = ( $atts['step'] !== '' ) ? floatval( $atts['step'] ) : apply_filters( 'woocommerce_quantity_input_step', 1, $wc_product );

    // Determine starting value: prefer attribute, else POSTed main input, else product min
    $value = ( $atts['value'] !== '' )
        ? floatval( $atts['value'] )
        : ( isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $min );

    if ( $value < $min ) $value = $min;
    if ( $max && $value > $max ) $value = $max;

    // Build args for a proxy input. Name is different so it doesn't interfere with add-to-cart POST.
    $args = array(
        'input_name'   => 'qty_proxy',
        'input_value'  => $value,
        'min_value'    => $min,
        'max_value'    => $max ? $max : '',
        'step'         => $step,
        'classes'      => array( 'qty', 'text', 'qty-proxy-input' ),
        'pattern'      => apply_filters( 'woocommerce_quantity_input_pattern', '[0-9]*' ),
        'inputmode'    => apply_filters( 'woocommerce_quantity_input_inputmode', 'numeric' ),
        'product_name' => $wc_product->get_name(),
    );

    // Capture and return the HTML (must echo the returned string)
    ob_start();
    echo '<div class="wc-qty-proxy ' . esc_attr( $atts['class'] ) . '" data-product-id="' . esc_attr( $wc_product->get_id() ) . '">';
    echo woocommerce_quantity_input( $args, $wc_product, false );
    echo '</div>';
    return ob_get_clean();
}



add_action( 'init', function() {
    add_shortcode( 'wc_product_labels', 'wcplqt_product_labels_shortcode' );
} );

/**
 * Shortcode.
 * [wc_product_labels id="123" sale_label="Sale" outofstock_label="Sold out"]
 */
function wcplqt_product_labels_shortcode( $atts ) {
    $atts = shortcode_atts(
        array(
            'id'               => 0,
            'sale_label'       => '',
            'outofstock_label' => '',
        ),
        $atts,
        'wc_product_labels'
    );

    $product = null;

    if ( $atts['id'] ) {
        $product = wc_get_product( (int) $atts['id'] );
    } elseif ( isset( $GLOBALS['product'] ) && is_a( $GLOBALS['product'], 'WC_Product' ) ) {
        $product = $GLOBALS['product'];
    }

    if ( ! $product ) {
        return '';
    }

    $labels       = array();
    $product_id   = $product->get_id();
    $sale_tmpl    = $atts['sale_label']       !== '' ? $atts['sale_label']       : __( 'Sale', 'woocommerce' );
    $oos_tmpl     = $atts['outofstock_label'] !== '' ? $atts['outofstock_label'] : __( 'Out of Stock', 'woocommerce' );

    $is_oos       = ! $product->is_in_stock();
    $is_on_sale   = $product->is_on_sale();

    // Out-of-stock: show ONLY the OOS badge.
    if ( $is_oos ) {
        $labels[] = '<span class="wc-badge wc-badge--out-of-stock">' . esc_html( $oos_tmpl ) . '</span>';
    } else {
        // In stock: show Sale badge if applicable.
        if ( $is_on_sale ) {
            $labels[] = '<span class="wc-badge wc-badge--sale">' . esc_html( $sale_tmpl ) . '</span>';
        }
    }

    $attrs = array(
        'class'           => 'wc-product-labels',
        'data-product-id' => (string) (int) $product_id,
    );

    $attr_html = '';
    foreach ( $attrs as $k => $v ) {
        $attr_html .= ' ' . esc_attr( $k ) . '="' . esc_attr( $v ) . '"';
    }

    // If no labels, return empty string (prevents an empty wrapper).
    if ( empty( $labels ) ) {
        return '';
    }

    $html = '<div' . $attr_html . '>' . implode( "\n", $labels ) . '</div>';

    return $html;
}

/**
 * CSS.
 */
add_action( 'wp_enqueue_scripts', function() {
    $css = "

";
    $handle = 'woocommerce-general';

    if ( wp_style_is( $handle, 'enqueued' ) || wp_style_is( $handle, 'registered' ) ) {
        wp_add_inline_style( $handle, $css );
    } else {
        add_action( 'wp_head', function() use ( $css ) {
            echo '<style id="wcplqt-inline">' . $css . '</style>';
        } );
    }
}, 20 );


/**
 * Plugin Name: AL — Woo Term Thumbnail (Loop-Safe) for Elementor
 * Description: Dynamic tag that returns the current loop item's WooCommerce product category image (works in Product Taxonomy Loop Grid).
 * Version:     1.0.0
 * Author:      Your Team
 */

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

add_action( 'elementor/dynamic_tags/register', function( $manager ) {

    // Simple image dynamic tag for term thumbnails
    class AL_Woo_Term_Thumb_Tag extends \Elementor\Core\DynamicTags\Data_Tag {

        public function get_name(): string { return 'al-woo-term-thumb'; }
        public function get_title(): string { return esc_html__( 'Woo Term Thumbnail (Loop-Safe)', 'al' ); }
        public function get_group(): array { return [ 'site' ]; }

        // Tell Elementor this tag returns an IMAGE so it appears on Image widgets
        public function get_categories(): array {
            return [ \Elementor\Modules\DynamicTags\Module::IMAGE_CATEGORY ];
        }

        public function get_value( array $options = [] ) {
            $term_id = 0;

            // Inside Elementor term loop, the current term lives here:
            global $wp_query;
            if ( isset( $wp_query->loop_term ) && isset( $wp_query->loop_term->term_id ) ) {
                $term_id = (int) $wp_query->loop_term->term_id;
            }

            // Fallback: on a product_cat archive, use the queried term
            if ( ! $term_id && is_tax( 'product_cat' ) ) {
                $term_id = (int) get_queried_object_id();
            }

            if ( ! $term_id ) {
                return false;
            }

            // WooCommerce stores category image ID in term meta key 'thumbnail_id'
            $attachment_id = (int) get_term_meta( $term_id, 'thumbnail_id', true );

            if ( ! $attachment_id ) {
                return false;
            }

            $url = wp_get_attachment_url( $attachment_id );
            if ( ! $url ) {
                return false;
            }

            // Elementor expects an array with id/url for IMAGE_CATEGORY tags
            return [
                'id'  => $attachment_id,
                'url' => $url,
            ];
        }
    }

    $manager->register( new AL_Woo_Term_Thumb_Tag() );
} );


/**
 * Change "Add to Cart" text on single product pages.
 */
add_filter( 'woocommerce_product_single_add_to_cart_text', 'wc_custom_single_add_to_cart_text', 10, 2 );

function wc_custom_single_add_to_cart_text( $text, $product ) {
    // Example: different text based on product type
    if ( $product->is_type( 'simple' ) ) {
        return __( 'Kosárba', 'your-textdomain' );
    }

    if ( $product->is_type( 'variable' ) ) {
        return __( 'Kosárba', 'your-textdomain' );
    }

    // Default fallback
    return __( 'Add to Basket', 'your-textdomain' );
}


/**
 * Plugin Name: Custom Frontend Signup Form (Shortcode)
 * Description: Provides [custom_signup_form] shortcode to register users. Customizable field labels, Terms, and logged-in notice via shortcode attributes.
 * Version:     1.3.0
 * Author:      Your Name
 * Text Domain: cfsf
 */

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

final class CFSF_Custom_Signup {

    const NONCE_ACTION = 'cfsf_signup_action';
    const NONCE_NAME   = 'cfsf_signup_nonce';
    const SHORTCODE    = 'custom_signup_form';
    const RATE_KEY     = 'cfsf_rate_';
    const RATE_MAX     = 5;      // submissions per hour
    const RATE_WINDOW  = 3600;   // seconds

    public function __construct() {
        add_shortcode( self::SHORTCODE, [ $this, 'render_shortcode' ] );
        add_action( 'init', [ $this, 'maybe_handle_post' ] );
    }

    /** Handle form submission early so we can redirect cleanly */
    public function maybe_handle_post() {
        if ( empty( $_POST['cfsf_submitted'] ) ) {
            return;
        }

        // Nonce
        if ( ! isset( $_POST[ self::NONCE_NAME ] ) || ! wp_verify_nonce( $_POST[ self::NONCE_NAME ], self::NONCE_ACTION ) ) {
            $this->store_error( __( 'Security check failed. Please refresh and try again.', 'cfsf' ) );
            return;
        }

        // Honeypot
        if ( ! empty( $_POST['cfsf_hp'] ) ) {
            $this->store_error( __( 'Spam detected.', 'cfsf' ) );
            return;
        }

        // Rate limiting
        if ( ! $this->check_rate_limit() ) {
            $this->store_error( __( 'Too many attempts. Please try again in a little while.', 'cfsf' ) );
            return;
        }

        // Redirect target
        $redirect = isset( $_POST['cfsf_redirect'] ) ? $this->sanitize_redirect( wp_unslash( $_POST['cfsf_redirect'] ) ) : home_url( '/' );

        // Logged-in visitors: just go where they asked
        if ( is_user_logged_in() ) {
            wp_safe_redirect( $redirect );
            exit;
        }

        // Inputs
        $first_name = isset( $_POST['first_name'] ) ? sanitize_text_field( wp_unslash( $_POST['first_name'] ) ) : '';
        $last_name  = isset( $_POST['last_name'] )  ? sanitize_text_field( wp_unslash( $_POST['last_name'] ) )  : '';
        $email      = isset( $_POST['email'] )      ? sanitize_email( wp_unslash( $_POST['email'] ) )           : '';
        $password   = isset( $_POST['password'] )   ? (string) $_POST['password']                               : '';
        $phone      = isset( $_POST['phone'] )      ? sanitize_text_field( wp_unslash( $_POST['phone'] ) )      : '';

        // Flags
        $require_phone = isset( $_POST['cfsf_require_phone'] ) && $_POST['cfsf_require_phone'] === 'yes';
        $require_terms = isset( $_POST['cfsf_require_terms'] ) && $_POST['cfsf_require_terms'] === 'yes';

        // Validate
        $errors = new WP_Error();

        if ( empty( $first_name ) ) {
            $errors->add( 'first_name', __( 'First name is required.', 'cfsf' ) );
        }
        if ( empty( $last_name ) ) {
            $errors->add( 'last_name', __( 'Last name is required.', 'cfsf' ) );
        }
        if ( empty( $email ) || ! is_email( $email ) ) {
            $errors->add( 'email', __( 'A valid email is required.', 'cfsf' ) );
        } elseif ( email_exists( $email ) ) {
            $errors->add( 'email_exists', __( 'An account with this email already exists.', 'cfsf' ) );
        }
        if ( empty( $password ) || strlen( $password ) < 8 ) {
            $errors->add( 'password', __( 'Password must be at least 8 characters.', 'cfsf' ) );
        }
        if ( $require_phone && empty( $phone ) ) {
            $errors->add( 'phone', __( 'Phone number is required.', 'cfsf' ) );
        }
        if ( $require_terms && empty( $_POST['terms_agree'] ) ) {
            $errors->add( 'terms', __( 'You must agree to the Terms to continue.', 'cfsf' ) );
        }

        if ( $errors->has_errors() ) {
            $this->store_error( $errors );
            return;
        }

        /** Hook before user creation */
        do_action( 'cfsf_before_create_user', $_POST );

        // Create user (email as login)
        $user_id = wp_create_user( $email, $password, $email );

        if ( is_wp_error( $user_id ) ) {
            $code = $user_id->get_error_code();
            if ( 'existing_user_login' === $code ) {
                $this->store_error( __( 'A user with that login already exists.', 'cfsf' ) );
            } elseif ( 'existing_user_email' === $code ) {
                $this->store_error( __( 'An account with this email already exists.', 'cfsf' ) );
            } else {
                $this->store_error( $user_id );
            }
            return;
        }

        // Profile basics
        wp_update_user( [
            'ID'         => $user_id,
            'first_name' => $first_name,
            'last_name'  => $last_name,
            'role'       => get_option( 'default_role', 'subscriber' ),
        ] );

        // Phone meta (generic only)
        if ( ! empty( $phone ) ) {
            update_user_meta( $user_id, 'phone', $phone );
        }

        /** Hook after user creation */
        do_action( 'cfsf_after_create_user', $user_id, $_POST );

        // Auto-login & redirect
        wp_set_current_user( $user_id );
        wp_set_auth_cookie( $user_id, true );
        do_action( 'wp_login', get_userdata( $user_id )->user_login, get_userdata( $user_id ) );

        wp_safe_redirect( $redirect ? $redirect : home_url( '/' ) );
        exit;
    }

    /** Render the signup form */
    public function render_shortcode( $atts ) {
        // Normalize keys to avoid builder case quirks
        $atts = array_change_key_case( (array) $atts, CASE_LOWER );

        $defaults = [
            // Behavior
            'require_phone' => 'no',
            'require_terms' => 'no',
            'redirect'      => '',

            // Labels (plain text)
            'label_first_name' => __( 'First Name', 'cfsf' ),
            'label_last_name'  => __( 'Last Name', 'cfsf' ),
            'label_email'      => __( 'Email', 'cfsf' ),
            'label_password'   => __( 'Password', 'cfsf' ),
            'label_phone'      => __( 'Phone', 'cfsf' ),
            'label_submit'     => __( 'Create Account', 'cfsf' ),

            // Terms: prefer label_terms_html if provided; otherwise build from url/text
            'label_terms_html' => '',
            'terms_url'        => '/terms',
            'terms_text'       => __( 'Terms & Conditions', 'cfsf' ),

            // Logged-in notice (NEW)
            'logged_in_message'    => __( 'You are already logged in.', 'cfsf' ),
            'logged_in_link_text'  => __( 'Go to my account', 'cfsf' ),
            'logged_in_link_url'   => '',

            // Legacy (ignored gracefully)
            'include_woo'      => '',
        ];

        $atts = shortcode_atts( $defaults, $atts, self::SHORTCODE );

        // Flags
        $require_phone = ( strtolower( $atts['require_phone'] ) === 'yes' );
        $require_terms = ( strtolower( $atts['require_terms'] ) === 'yes' );

        // Redirect (allow relative URLs)
        $redirect = ! empty( $atts['redirect'] ) ? esc_url( $this->sanitize_redirect( $atts['redirect'] ) ) : esc_url( home_url( '/' ) );

        // Helper: decode any builder-escaped values
        $decode = static function( $val ) {
            return trim( wp_specialchars_decode( (string) $val, ENT_QUOTES ) );
        };

        // Labels (decoded; we escape on output)
        $label_first_name = $decode( $atts['label_first_name'] );
        $label_last_name  = $decode( $atts['label_last_name'] );
        $label_email      = $decode( $atts['label_email'] );
        $label_password   = $decode( $atts['label_password'] );
        $label_phone      = $decode( $atts['label_phone'] );
        $label_submit     = $decode( $atts['label_submit'] );

        // Terms label (HTML allowed)
        $label_terms_html = $decode( $atts['label_terms_html'] );
        if ( $label_terms_html === '' ) {
            $terms_url  = esc_url( $decode( $atts['terms_url'] ) );
            $terms_text = esc_html( $decode( $atts['terms_text'] ) );
            $label_terms_html = sprintf(
                'I agree to the <a href="%s" target="_blank" rel="noopener">%s</a>',
                $terms_url ?: esc_url( home_url( '/terms' ) ),
                $terms_text ?: esc_html__( 'Terms & Conditions', 'cfsf' )
            );
        }

        // Logged-in text (NEW)
        $logged_in_message   = $decode( $atts['logged_in_message'] );
        $logged_in_link_text = $decode( $atts['logged_in_link_text'] );

        // Default account URL: Woo My Account → WP profile → homepage
        $default_account_url = function_exists( 'wc_get_page_permalink' )
            ? wc_get_page_permalink( 'myaccount' )
            : admin_url( 'profile.php' );
        if ( empty( $default_account_url ) ) {
            $default_account_url = home_url( '/' );
        }

        $logged_in_link_url  = $atts['logged_in_link_url'] !== ''
            ? esc_url( $this->sanitize_redirect( $decode( $atts['logged_in_link_url'] ) ) )
            : esc_url( $default_account_url );

        ob_start();

        // If logged in, show customizable notice instead of the form
        if ( is_user_logged_in() ) : ?>
            <div class="cfsf-success">
                <?php echo esc_html( $logged_in_message ); ?>
                <?php if ( $logged_in_link_url ) : ?>
                    <a href="<?php echo esc_url( $logged_in_link_url ); ?>"><?php echo esc_html( $logged_in_link_text ); ?></a>
                <?php endif; ?>
            </div>
            <?php
            return ob_get_clean();
        endif;

        // Show stored errors
        $this->output_errors();
        ?>

        <form method="post" class="cfsf-signup-form" action="">
            <?php wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME ); ?>
            <input type="hidden" name="cfsf_submitted" value="1" />
            <input type="text" name="cfsf_hp" value="" style="display:none!important;" tabindex="-1" autocomplete="off" aria-hidden="true" />
            <input type="hidden" name="cfsf_require_phone" value="<?php echo $require_phone ? 'yes' : 'no'; ?>" />
            <input type="hidden" name="cfsf_require_terms" value="<?php echo $require_terms ? 'yes' : 'no'; ?>" />
            <input type="hidden" name="cfsf_redirect" value="<?php echo esc_url( $redirect ); ?>" />

            <div class="cfsf-row">
                <label for="first_name"><?php echo esc_html( $label_first_name ); ?> *</label>
                <input type="text" id="first_name" name="first_name" required autocomplete="given-name" />
            </div>

            <div class="cfsf-row">
                <label for="last_name"><?php echo esc_html( $label_last_name ); ?> *</label>
                <input type="text" id="last_name" name="last_name" required autocomplete="family-name" />
            </div>

            <div class="cfsf-row">
                <label for="email"><?php echo esc_html( $label_email ); ?> *</label>
                <input type="email" id="email" name="email" required autocomplete="email" />
            </div>

            <div class="cfsf-row">
                <label for="password"><?php echo esc_html( $label_password ); ?> *</label>
                <input type="password" id="password" name="password" minlength="8" required autocomplete="new-password" />
            </div>

            <div class="cfsf-row">
                <label for="phone"><?php echo esc_html( $label_phone ); ?><?php echo $require_phone ? ' *' : ''; ?></label>
                <input type="tel" id="phone" name="phone" <?php echo $require_phone ? 'required' : ''; ?> autocomplete="tel" />
            </div>

            <?php if ( $require_terms ) : ?>
                <div class="cfsf-row cfsf-terms">
                    <label class="cfsf-terms-label">
                        <input type="checkbox" name="terms_agree" value="1" />
                        <?php
                        echo wp_kses(
                            $label_terms_html,
                            [
                                'a'      => [ 'href' => [], 'target' => [], 'rel' => [] ],
                                'strong' => [],
                                'em'     => [],
                                'span'   => [ 'class' => [] ],
                            ]
                        );
                        ?>
                    </label>
                </div>
            <?php endif; ?>

            <div class="cfsf-row">
                <button type="submit"><?php echo esc_html( $label_submit ); ?></button>
            </div>
        </form>

        <style>

        </style>
        <?php

        return ob_get_clean();
    }

    /** Store errors to transient for post-redirect display */
    private function store_error( $error ) {
        $msgs = [];
        if ( is_wp_error( $error ) ) {
            $msgs = $error->get_error_messages();
        } else {
            $msgs = [ (string) $error ];
        }
        set_transient( 'cfsf_errors_' . $this->session_key(), $msgs, MINUTE_IN_SECONDS * 5 );
    }

    /** Output & clear stored errors */
    private function output_errors() {
        $key    = 'cfsf_errors_' . $this->session_key();
        $errors = get_transient( $key );
        if ( $errors && is_array( $errors ) && ! empty( $errors ) ) {
            echo '<div class="cfsf-errors"><strong>' . esc_html__( 'Please fix the following:', 'cfsf' ) . '</strong><ul>';
            foreach ( $errors as $msg ) {
                echo '<li>' . esc_html( $msg ) . '</li>';
            }
            echo '</ul></div>';
            delete_transient( $key );
        }
    }

    /** Lightweight per-visitor session key without PHP sessions */
    private function session_key() {
        if ( ! isset( $_COOKIE['cfsf_key'] ) ) {
            $val = wp_generate_password( 20, false );
            setcookie( 'cfsf_key', $val, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
            $_COOKIE['cfsf_key'] = $val;
        }
        return sanitize_text_field( $_COOKIE['cfsf_key'] );
    }

    private function client_ip() {
        $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
        return sanitize_text_field( $ip );
    }

    private function check_rate_limit() : bool {
        $ip   = $this->client_ip();
        $key  = self::RATE_KEY . md5( $ip );
        $data = get_transient( $key );

        if ( empty( $data ) || ! is_array( $data ) ) {
            set_transient( $key, [ 'count' => 1, 'start' => time() ], self::RATE_WINDOW );
            return true;
        }

        $count = isset( $data['count'] ) ? (int) $data['count'] : 0;
        $start = isset( $data['start'] ) ? (int) $data['start'] : time();

        if ( ( time() - $start ) > self::RATE_WINDOW ) {
            set_transient( $key, [ 'count' => 1, 'start' => time() ], self::RATE_WINDOW );
            return true;
        }

        if ( $count >= self::RATE_MAX ) {
            return false;
        }

        $data['count'] = $count + 1;
        set_transient( $key, $data, self::RATE_WINDOW - ( time() - $start ) );
        return true;
    }

    /** Only allow same-host redirects (extend with your own whitelist if needed). */
    private function sanitize_redirect( $url ) {
        $url = esc_url_raw( $url );
        if ( empty( $url ) ) {
            return home_url( '/' );
        }
        $host_allowed = parse_url( home_url(), PHP_URL_HOST );
        $host_target  = parse_url( $url, PHP_URL_HOST );
        if ( $host_target && ! hash_equals( $host_allowed, $host_target ) ) {
            return home_url( '/' );
        }
        return $url;
    }
}

new CFSF_Custom_Signup();

// Add "Quantity" label above the quantity input field on single product page
add_action( 'woocommerce_before_add_to_cart_quantity', 'custom_quantity_label' );
function custom_quantity_label() {
    echo '<label for="quantity" class="quantity-label" style="display:block;margin-bottom:5px;font-weight:600;">' . __( 'Quantity', 'woocommerce' ) . '</label>';
}
/* Display an order bump before the order review section on the WooCommerce checkout page. */
function aovup_custom_order_bump() {
    // Check if it's the checkout page

    echo '<div class="custom-order-bump">';
    echo do_shortcode('[cuw_checkout_upsells]');
    echo '</div>';

}
add_action('woocommerce_checkout_before_order_review', 'aovup_custom_order_bump');


/**
 * Plugin Name: AL — Woo + Elementor Product Filter Bar (Multi-Grid Scoped + AJAX Rehydrate + Modern Preloader)
 * Description: Filter bar for Woo/Elementor Loop Grid. Per-instance namespacing so multiple filter bars & grids on the same page work independently. Archive-aware, preserves params, rehydrates Elementor, accessible UI. Includes optional "source" (e.g. sale) and per-QueryID default source mapping for first-paint correctness. Brand logos, empty-state messages, and performance/a11y improvements included.
 * Version: 1.8.4
 * Author: Art & Living
 * Text Domain: al-woo-ele-filter
 */

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

/** =========================
 *  Dynamic Elementor Query ID registry
 *  ========================= */
if ( ! function_exists( 'al_pf_register_elementor_query_id' ) ) {
    function al_pf_register_elementor_query_id( $ids ) {
        static $registered = array();
        $ids = is_array( $ids ) ? $ids : array( $ids );
        foreach ( $ids as $raw ) {
            $id = sanitize_key( $raw );
            if ( ! $id || isset( $registered[ $id ] ) ) continue;
            add_action( "elementor/query/{$id}",            'al_pf_elementor_posts_query', 5 );
            add_action( "elementor_pro/posts/query/{$id}",  'al_pf_elementor_posts_query', 5 );
            $registered[ $id ] = true;
        }
    }
}
// Defaults for compatibility (+ pre-register sale_query_id to ensure early hook availability)
al_pf_register_elementor_query_id( array( 'archive_loop_grid', 'alpf', 'sale_query_id' ) );

/** =========================
 *  Param helpers (namespaced)
 *  ========================= */
if ( ! function_exists( 'alpf_get_param' ) ) {
    function alpf_get_param( $key, $ns = '' ) {
        $key = (string) $key;
        $ns  = $ns ? sanitize_key( $ns ) : '';
        if ( $ns ) {
            $nk = "{$ns}_{$key}";
            if ( isset( $_GET[ $nk ] ) && $_GET[ $nk ] !== '' ) {
                return $_GET[ $nk ];
            }
        }
        return isset( $_GET[ $key ] ) ? $_GET[ $key ] : null;
    }
}
if ( ! function_exists( 'alpf_get_param_list' ) ) {
    function alpf_get_param_list( $key, $ns = '' ) {
        $val = alpf_get_param( $key, $ns );
        if ( $val === null || $val === '' ) return array();
        $arr = array_map( 'trim', explode( ',', (string) wp_unslash( $val ) ) );
        return array_values( array_filter( $arr, 'strlen' ) );
    }
}

/** =========================
 *  Helpers
 *  ========================= */
if ( ! function_exists( 'al_pf_get_brand_taxonomy' ) ) {
    function al_pf_get_brand_taxonomy( $preferred = '' ) {
        $preferred = $preferred ? sanitize_key( $preferred ) : '';
        if ( $preferred && taxonomy_exists( $preferred ) ) return $preferred;
        if ( taxonomy_exists( 'product_brand' ) )       return 'product_brand';
        if ( taxonomy_exists( 'yith_product_brand' ) )  return 'yith_product_brand';
        if ( taxonomy_exists( 'pa_brand' ) )            return 'pa_brand';
        return '';
    }
}

/** Detect namespaced/un-namespaced PF params anywhere in query string */
if ( ! function_exists( 'alpf_request_has_pf_params_globally' ) ) {
    function alpf_request_has_pf_params_globally() {
        foreach ( (array) $_GET as $k => $v ) {
            if ( ! is_string( $k ) ) continue;
            if ( preg_match( '/(?:^|_)pf_(?:cat|brand|attr|brand_tax|source|min_price|max_price)\z/', $k ) ) {
                if ( $v !== '' && $v !== null ) return true;
            }
        }
        return false;
    }
}

/** =========================
 *  Brand/logo helpers
 *  ========================= */
if ( ! function_exists( 'al_pf_get_term_logo_url' ) ) {
    /**
     * Resolve a brand term's logo URL using common meta keys.
     * Filters:
     *  - alpf_brand_logo_meta_keys : array of possible meta keys for logo attachment id
     *  - alpf_brand_logo_url       : override the resolved URL
     */
    function al_pf_get_term_logo_url( $term_id ) {
        $keys = apply_filters( 'alpf_brand_logo_meta_keys', array(
            'thumbnail_id',
            'brand_thumbnail_id',
            'brand_logo_id',
            'logo_id',
        ) );
        $url = '';
        foreach ( (array) $keys as $meta_key ) {
            $att_id = absint( get_term_meta( $term_id, $meta_key, true ) );
            if ( $att_id ) {
                $src = wp_get_attachment_image_src( $att_id, 'thumbnail' );
                if ( $src && ! empty( $src[0] ) ) { $url = $src[0]; break; }
            }
        }
        return (string) apply_filters( 'alpf_brand_logo_url', $url, $term_id );
    }
}

/** =========================
 *  Source handling (sale / all) — URL param or default map
 *  ========================= */
if ( ! function_exists( 'alpf_get_effective_source' ) ) {
    function alpf_get_effective_source( $ns = '' ) {
        $ns = $ns ? sanitize_key( $ns ) : '';
        $src = alpf_get_param( 'pf_source', $ns );
        if ( $src !== null && $src !== '' ) {
            return sanitize_key( wp_unslash( $src ) );
        }
        $map = apply_filters( 'alpf_default_source_map', array() );
        if ( is_array( $map ) ) {
            if ( $ns && ! empty( $map[ $ns ] ) ) return sanitize_key( $map[ $ns ] );
            if ( isset( $map[''] ) && $map[''] ) return sanitize_key( $map[''] );
        }
        return '';
    }
}

if ( ! function_exists( 'al_pf_get_sale_ids' ) ) {
    function al_pf_get_sale_ids() {
        // Cached for performance on large catalogs
        $cache_key = 'alpf_sale_ids_v1';
        $cached    = get_transient( $cache_key );
        if ( is_array( $cached ) ) return $cached;

        $ids = function_exists( 'wc_get_product_ids_on_sale' ) ? wc_get_product_ids_on_sale() : array();
        $ids = is_array( $ids ) ? array_map( 'intval', $ids ) : array();

        $ttl = (int) apply_filters( 'alpf_sale_ids_ttl', 5 * MINUTE_IN_SECONDS );
        set_transient( $cache_key, $ids, $ttl );
        return $ids;
    }
}

if ( ! function_exists( 'al_pf_apply_source_to_args' ) ) {
    function al_pf_apply_source_to_args( $args, $ns = '' ) {
        $src = alpf_get_effective_source( $ns );
        if ( $src === 'sale' ) {
            $sale_ids = al_pf_get_sale_ids();
            if ( ! empty( $sale_ids ) ) {
                $args['post__in'] = $sale_ids;
            }
        }
        return $args;
    }
}

/** =========================
 *  Query parts
 *  ========================= */
if ( ! function_exists( 'al_pf_build_tax_query_parts' ) ) {
    function al_pf_build_tax_query_parts( $brand_tax = '', $ns = '' ) {
        $parts = array();

        $cats_raw = alpf_get_param_list( 'pf_cat', $ns );
        if ( $cats_raw ) {
            $cats = array_map( 'sanitize_title', $cats_raw );
            if ( $cats ) $parts[] = array( 'taxonomy'=>'product_cat', 'field'=>'slug', 'terms'=>$cats, 'operator'=>'IN' );
        }

        if ( ! $brand_tax ) {
            $brand_tax_raw = alpf_get_param( 'pf_brand_tax', $ns );
            if ( ! empty( $brand_tax_raw ) ) {
                $brand_tax = sanitize_key( wp_unslash( $brand_tax_raw ) );
                if ( ! taxonomy_exists( $brand_tax ) ) $brand_tax = '';
            }
            if ( ! $brand_tax ) $brand_tax = al_pf_get_brand_taxonomy();
        }

        $brands_raw = alpf_get_param_list( 'pf_brand', $ns );
        if ( $brand_tax && $brands_raw ) {
            $brands = array_map( 'sanitize_title', $brands_raw );
            if ( $brands ) $parts[] = array( 'taxonomy'=>$brand_tax, 'field'=>'slug', 'terms'=>$brands, 'operator'=>'IN' );
        }

        $attr_raw = alpf_get_param_list( 'pf_attr', $ns );
        if ( $attr_raw ) {
            $attr_terms_by_tax = array();
            foreach ( $attr_raw as $p ) {
                if ( strpos( $p, ':' ) === false ) continue;
                list( $tax, $slug ) = array_map( 'sanitize_key', explode( ':', $p, 2 ) );
                if ( $tax && $slug && taxonomy_exists( $tax ) ) $attr_terms_by_tax[ $tax ][] = $slug;
            }
            foreach ( $attr_terms_by_tax as $tax => $slugs ) {
                $parts[] = array(
                    'taxonomy' => $tax,
                    'field'    => 'slug',
                    'terms'    => array_values( array_unique( $slugs ) ),
                    'operator' => 'IN',
                );
            }
        }

        return $parts;
    }
}

if ( ! function_exists( 'alpf_build_catalog_guards' ) ) {
    function alpf_build_catalog_guards( $is_search = false, $ns = '' ) {
        $tax_query  = array();
        $meta_query = array();

        $vis_terms = array( 'exclude-from-catalog' );
        if ( $is_search ) $vis_terms[] = 'exclude-from-search';
        $tax_query[] = array(
            'taxonomy' => 'product_visibility',
            'field'    => 'name',
            'terms'    => $vis_terms,
            'operator' => 'NOT IN',
        );

        if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
            $meta_query[] = array(
                'key'     => '_stock_status',
                'value'   => 'outofstock',
                'compare' => '!=',
            );
            $tax_query[] = array(
                'taxonomy' => 'product_visibility',
                'field'    => 'name',
                'terms'    => array( 'outofstock' ),
                'operator' => 'NOT IN',
            );
        }

        // Safer price bounds
        $min_price_raw = alpf_get_param( 'min_price', $ns );
        $max_price_raw = alpf_get_param( 'max_price', $ns );
        $has_min = ($min_price_raw !== null && $min_price_raw !== '');
        $has_max = ($max_price_raw !== null && $max_price_raw !== '');
        if ( $has_min || $has_max ) {
            $min = $has_min ? max( 0, floatval( wp_unslash( $min_price_raw ) ) ) : 0;
            $max = $has_max ? max( $min, floatval( wp_unslash( $max_price_raw ) ) ) : $min;
            $meta_query[] = array(
                'key'     => '_price',
                'type'    => 'DECIMAL',
                'compare' => 'BETWEEN',
                'value'   => array( (string) $min, (string) $max ),
            );
        }

        return array( $tax_query, $meta_query );
    }
}

/** =========================
 *  Collect terms for UI (respects effective source)
 *  ========================= */
if ( ! function_exists( 'al_pf_collect_terms_for_filters' ) ) {
    function al_pf_collect_terms_for_filters( $brand_tax, $attr_tax_slugs, $context = array(), $ns = '' ) {
        $s_query    = alpf_get_param( 's', $ns );
        $s_query    = $s_query ? sanitize_text_field( wp_unslash( $s_query ) ) : '';

        $pf_cat_s   = alpf_get_param( 'pf_cat', $ns );
        $pf_brand_s = alpf_get_param( 'pf_brand', $ns );
        $pf_attr_s  = alpf_get_param( 'pf_attr', $ns );

        $ctx_tax   = '';
        $ctx_terms = array();
        if ( empty( $context ) ) {
            if ( function_exists( 'is_tax' ) && is_tax() ) {
                $qo = get_queried_object();
                if ( $qo && ! empty( $qo->taxonomy ) && taxonomy_exists( $qo->taxonomy ) && is_object_in_taxonomy( 'product', $qo->taxonomy ) ) {
                    $ctx_tax   = $qo->taxonomy;
                    $ctx_terms = array( $qo->slug );
                }
            }
        } else {
            $ctx_tax   = ! empty( $context['tax'] )   ? sanitize_key( $context['tax'] ) : '';
            $ctx_terms = ! empty( $context['terms'] ) ? array_filter( array_map( 'sanitize_title', (array) $context['terms'] ) ) : array();
        }

        $pf_cats   = array_filter( array_map( 'sanitize_title', array_map( 'trim', explode( ',', (string) $pf_cat_s ) ) ) );
        $pf_brands = array_filter( array_map( 'sanitize_title', array_map( 'trim', explode( ',', (string) $pf_brand_s ) ) ) );

        $attr_terms_by_tax = array();
        if ( $pf_attr_s ) {
            foreach ( array_map( 'trim', explode( ',', (string) $pf_attr_s ) ) as $p ) {
                if ( strpos( $p, ':' ) === false ) continue;
                list( $tax, $slug ) = array_map( 'sanitize_key', explode( ':', $p, 2 ) );
                if ( $tax && $slug && taxonomy_exists( $tax ) ) $attr_terms_by_tax[ $tax ][] = $slug;
            }
        }

        $tax_query = array();
        if ( $ctx_tax && $ctx_terms ) {
            $tax_query[] = array(
                'taxonomy' => $ctx_tax,
                'field'    => 'slug',
                'terms'    => $ctx_terms,
                'operator' => 'IN',
            );
        }
        if ( $pf_cats )   $tax_query[] = array( 'taxonomy'=>'product_cat', 'field'=>'slug', 'terms'=>$pf_cats, 'operator'=>'IN' );
        if ( $brand_tax && $pf_brands ) $tax_query[] = array( 'taxonomy'=>$brand_tax, 'field'=>'slug', 'terms'=>$pf_brands, 'operator'=>'IN' );
        if ( $attr_terms_by_tax ) {
            foreach ( $attr_terms_by_tax as $tax => $slugs ) {
                $tax_query[] = array(
                    'taxonomy' => $tax,
                    'field'=>'slug',
                    'terms'    => array_values( array_unique( $slugs ) ),
                    'operator' => 'IN',
                );
            }
        }
        if ( count( $tax_query ) > 1 ) $tax_query = array_merge( array( 'relation' => 'AND' ), $tax_query );

        list( $guard_tax, $guard_meta ) = alpf_build_catalog_guards( ! empty( $s_query ), $ns );

        $extra_key_bits = (string) apply_filters( 'alpf_transient_key_extra', '' );

        $cache_key = 'al_pf_terms_' . md5( wp_json_encode( array(
                    'ns' => $ns,
                    's' => $s_query,
                    'cats' => $pf_cats,
                    'brand_tax' => $brand_tax,
                    'brands' => $pf_brands,
                    'attrs' => $attr_terms_by_tax,
                    'ctx' => array( 'tax' => $ctx_tax, 'terms' => $ctx_terms ),
                    'expose' => array_values( array_unique( (array) $attr_tax_slugs ) ),
                    'source' => (string) alpf_get_effective_source($ns),
                    'ver' => 'v24-mobile-no-overlay-gap'
                ) ) . '|' . $extra_key_bits );
        $cached = get_transient( $cache_key );
        if ( is_array( $cached ) ) return $cached;

        $final_tax = array_merge( (array) $tax_query, (array) $guard_tax );
        if ( count( $final_tax ) > 1 && ! isset( $final_tax['relation'] ) ) {
            $final_tax = array_merge( array( 'relation' => 'AND' ), $final_tax );
        }

        // Optional SKU search (off by default)
        $include_sku = (bool) apply_filters( 'alpf_scan_include_sku', false );
        $scan_meta   = $guard_meta;
        if ( $s_query && $include_sku ) {
            $scan_meta = array_merge(
                array( 'relation' => 'AND' ),
                (array) $guard_meta,
                array(
                    array(
                        'relation' => 'OR',
                        array(
                            'key'     => '_sku',
                            'value'   => $s_query,
                            'compare' => 'LIKE',
                        ),
                    )
                )
            );
        }

        $scan_args = array(
            'post_type'              => 'product',
            'post_status'            => 'publish',
            's'                      => $s_query,
            'fields'                 => 'ids',
            'posts_per_page'         => (int) apply_filters( 'al_pf_terms_scan_posts_cap', 5000 ),
            'no_found_rows'          => true,
            'ignore_sticky_posts'    => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'cache_results'          => false,
            'orderby'                => 'none',
            'tax_query'              => $final_tax,
            'meta_query'             => $scan_meta,
        );

        // Apply "effective source" (e.g., sale) for term scan
        $scan_args = al_pf_apply_source_to_args( $scan_args, $ns );

        $scan = new WP_Query( $scan_args );
        $ids = ! is_wp_error( $scan ) ? array_map( 'intval', $scan->posts ) : array();

        $max_ids = (int) apply_filters( 'al_pf_terms_scan_cap', 5000 );
        if ( count( $ids ) > $max_ids ) {
            $out = array( 'cats'=>array(), 'brands'=>array(), 'attrs'=>array() );
            set_transient( $cache_key, $out, (int) apply_filters( 'al_pf_terms_ttl', 30 ) );
            if ( ! function_exists( 'alpf_index_transient' ) ) { function alpf_index_transient( $k ){} }
            alpf_index_transient( $cache_key );
            return $out;
        }

        $out = array( 'cats' => array(), 'brands' => array(), 'attrs' => array() );
        if ( empty( $ids ) ) {
            set_transient( $cache_key, $out, (int) apply_filters( 'al_pf_terms_ttl', 30 ) );
            if ( ! function_exists( 'alpf_index_transient' ) ) { function alpf_index_transient( $k ){} }
            alpf_index_transient( $cache_key );
            return $out;
        }

        /**
         * UPDATED BEHAVIOR (v1.8.4):
         * - On a parent product_cat archive: show only the direct children that actually have matching products.
         * - On a sub-category archive: show only that sub-category itself (no siblings).
         * - Outside product_cat contexts: fallback to original "all categories from scanned products".
         */
        $cats = array();
        $did_scope_to_children = false;

        if ( $ctx_tax === 'product_cat' && ! empty( $ctx_terms ) && count( $ctx_terms ) === 1 ) {
            $current_term = get_term_by( 'slug', $ctx_terms[0], 'product_cat' );

            if ( $current_term && ! is_wp_error( $current_term ) ) {
                // SUB-CATEGORY: show only this sub-category name
                if ( isset( $current_term->parent ) && (int) $current_term->parent > 0 ) {
                    $cats = array( $current_term );
                    $did_scope_to_children = true;
                } else {
                    // PARENT CATEGORY: show only direct children with products
                    $args = array(
                        'taxonomy'   => 'product_cat',
                        'parent'     => (int) $current_term->term_id,
                        'orderby'    => 'name',
                        'order'      => 'ASC',
                        'hide_empty' => true,
                    );
                    if ( ! empty( $ids ) ) {
                        $args['object_ids'] = $ids; // bind to matched products
                    }
                    $children = get_terms( $args );
                    if ( ! is_wp_error( $children ) && ! empty( $children ) ) {
                        $cats = $children;
                        $did_scope_to_children = true;
                    } else {
                        // No children with products -> empty list (UI shows empty-state)
                        $cats = array();
                        $did_scope_to_children = true;
                    }
                }
            }
        }

        // Fallback ONLY when not on product_cat context
        if ( ! $did_scope_to_children ) {
            $cats = wp_get_object_terms( $ids, 'product_cat', array( 'hide_empty' => false ) );
        }

        if ( ! is_wp_error( $cats ) && $cats ) $out['cats'] = $cats;

        if ( $brand_tax ) {
            $brands = wp_get_object_terms( $ids, $brand_tax, array( 'hide_empty' => false ) );
            if ( ! is_wp_error( $brands ) && $brands ) $out['brands'] = $brands;
        }

        $attr_tax_slugs = array_values( array_unique( (array) $attr_tax_slugs ) );
        foreach ( $attr_tax_slugs as $tax ) {
            $terms = wp_get_object_terms( $ids, $tax, array( 'hide_empty' => false ) );
            if ( ! is_wp_error( $terms ) && $terms ) $out['attrs'][ $tax ] = $terms;
        }

        set_transient( $cache_key, $out, (int) apply_filters( 'al_pf_terms_ttl', 30 ) );
        if ( ! function_exists( 'alpf_index_transient' ) ) { function alpf_index_transient( $k ){} }
        alpf_index_transient( $cache_key );
        return $out;
    }
}

/** =========================
 *  Shortcode (UI)
 *  =========================
 * query_id, param_ns, source, label overrides...
 */
if ( ! function_exists( 'al_pf_filter_bar_shortcode' ) ) {
    function al_pf_filter_bar_shortcode( $atts ) {
        if ( ! class_exists( 'WooCommerce' ) ) return '<!-- WooCommerce not active -->';

        $atts = shortcode_atts( array(
            'brand_tax'        => '',
            'attributes'       => 'all',
            'show_reset'       => 'yes',
            'target'           => '',
            'query_id'         => '',
            'param_ns'         => '',
            'source'           => 'all', // 'all' (default) or 'sale'
            'label_category'   => '',
            'label_brand'      => '',
            'label_attributes' => '',
            'label_reset'      => '',
        ), $atts, 'wc_filter_bar' );

        // Register custom Query IDs
        $query_ids = array();
        if ( ! empty( $atts['query_id'] ) ) {
            foreach ( array_map( 'trim', explode( ',', (string) $atts['query_id'] ) ) as $qid ) {
                $k = sanitize_key( $qid );
                if ( $k ) $query_ids[] = $k;
            }
            if ( $query_ids ) al_pf_register_elementor_query_id( $query_ids );
        }

        // Determine namespace for this instance (param_ns > first query_id > '')
        $ns = '';
        if ( ! empty( $atts['param_ns'] ) ) {
            $ns = sanitize_key( $atts['param_ns'] );
        } elseif ( ! empty( $query_ids ) ) {
            $ns = $query_ids[0];
        }

        // Auto-target: if no 'target' given but we have a single query_id, assume CSS ID equals query_id
        $target_attr = trim( (string) $atts['target'] );
        if ( ! $target_attr && ! empty( $query_ids ) ) {
            $target_attr = '#' . $query_ids[0];
        }

        $forced_brand_tax = $atts['brand_tax'] ? sanitize_key( $atts['brand_tax'] ) : '';
        $brand_tax        = al_pf_get_brand_taxonomy( $forced_brand_tax );

        $attr_tax_slugs = array();
        if ( 'all' === strtolower( $atts['attributes'] ) ) {
            if ( function_exists( 'wc_get_attribute_taxonomies' ) ) {
                $global_attrs = wc_get_attribute_taxonomies();
                if ( $global_attrs ) {
                    foreach ( $global_attrs as $ga ) {
                        $tax = 'pa_' . $ga->attribute_name;
                        if ( taxonomy_exists( $tax ) ) $attr_tax_slugs[] = $tax;
                    }
                }
            }
        } else {
            foreach ( explode( ',', $atts['attributes'] ) as $maybe ) {
                $tax = sanitize_key( trim( $maybe ) );
                if ( $tax && taxonomy_exists( $tax ) ) $attr_tax_slugs[] = $tax;
            }
        }
        $attr_tax_slugs = array_values( array_unique( $attr_tax_slugs ) );

        // Context (taxonomy archive)
        $ctx_tax   = '';
        $ctx_terms = array();
        if ( function_exists('is_tax') && is_tax() ) {
            $qo = get_queried_object();
            if ( $qo && ! empty( $qo->taxonomy ) && taxonomy_exists( $qo->taxonomy ) && is_object_in_taxonomy( 'product', $qo->taxonomy ) ) {
                $ctx_tax   = $qo->taxonomy;
                $ctx_terms = array( $qo->slug );
            }
        }

        $present = al_pf_collect_terms_for_filters( $brand_tax, $attr_tax_slugs, array(
            'tax'   => $ctx_tax,
            'terms' => $ctx_terms,
        ), $ns );

        $sel_cat_s     = alpf_get_param( 'pf_cat',  $ns );
        $sel_brand_s   = alpf_get_param( 'pf_brand',$ns );
        $sel_attr_s    = alpf_get_param( 'pf_attr', $ns );
        $sel_cats      = $sel_cat_s  ? array_filter( array_map( 'sanitize_title', array_map( 'trim', explode( ',', (string) $sel_cat_s ) ) ) ) : array();
        $sel_brands    = $sel_brand_s? array_filter( array_map( 'sanitize_title', array_map( 'trim', explode( ',', (string) $sel_brand_s ) ) ) ) : array();
        $sel_attrs     = $sel_attr_s ? array_filter( array_map( 'sanitize_key',  array_map( 'trim', explode( ',', (string) $sel_attr_s ) ) ) )     : array();

        $sel_brand_tax = $brand_tax ? $brand_tax : ( alpf_get_param( 'pf_brand_tax', $ns ) ? sanitize_key( wp_unslash( alpf_get_param( 'pf_brand_tax', $ns ) ) ) : '' );
        $search_term   = alpf_get_param( 's', $ns );
        $search_term   = $search_term ? sanitize_text_field( wp_unslash( $search_term ) ) : get_search_query();

        $attr_total_terms = 0;
        foreach ( (array) $present['attrs'] as $terms ) { if ( is_array( $terms ) ) $attr_total_terms += count( $terms ); }

        $instance_id = 'alpf-' . wp_generate_uuid4();
        $nonce       = wp_create_nonce( 'alpf_terms' );

        // ARIA ids
        $cats_dd_id   = $instance_id . '-cats';
        $brand_dd_id  = $instance_id . '-brands';
        $attrs_dd_id  = $instance_id . '-attrs';

        // Label overrides (i18n)
        $label_category   = $atts['label_category']   !== '' ? sanitize_text_field( wp_unslash( $atts['label_category'] ) )   : __( 'Category', 'al-woo-ele-filter' );
        $label_brand      = $atts['label_brand']      !== '' ? sanitize_text_field( wp_unslash( $atts['label_brand'] ) )      : __( 'Brand', 'al-woo-ele-filter' );
        $label_attributes = $atts['label_attributes'] !== '' ? sanitize_text_field( wp_unslash( $atts['label_attributes'] ) ) : __( 'Attributes', 'al-woo-ele-filter' );
        $label_reset      = $atts['label_reset']      !== '' ? sanitize_text_field( wp_unslash( $atts['label_reset'] ) )      : __( 'Reset', 'al-woo-ele-filter' );
        $empty_msg        = __( 'Nincs lehetőség a jelenlegi kiválasztáshoz.', 'al-woo-ele-filter' );

        // Source handling
        $source_attr       = strtolower( sanitize_key( $atts['source'] ) );
        if ( ! in_array( $source_attr, array( 'all', 'sale' ), true ) ) $source_attr = 'all';
        $effective_source  = alpf_get_effective_source( $ns );
        $resolved_source   = ( $source_attr !== 'all' ) ? $source_attr : $effective_source;

        ob_start(); ?>
        <div class="al-pf-wrap"
             data-pf
             id="<?php echo esc_attr( $instance_id ); ?>"
             data-target="<?php echo esc_attr( $target_attr ); ?>"
             data-expose="<?php echo esc_attr( implode( ',', $attr_tax_slugs ) ); ?>"
             data-nonce="<?php echo esc_attr( $nonce ); ?>"
             data-ctx-tax="<?php echo esc_attr( $ctx_tax ); ?>"
             data-ctx-terms="<?php echo esc_attr( implode( ',', $ctx_terms ) ); ?>"
            <?php if ( $ns ) : ?> data-ns="<?php echo esc_attr( $ns ); ?>"<?php endif; ?>>

            <form class="al-pf-form" onsubmit="return false;" role="search" aria-label="<?php echo esc_attr__( 'Product filters', 'al-woo-ele-filter' ); ?>">
                <?php if ( $search_term ) : ?>
                    <input type="hidden" data-param="s" value="<?php echo esc_attr( $search_term ); ?>">
                <?php endif; ?>
                <?php if ( $sel_brand_tax ) : ?>
                    <input type="hidden" data-param="pf_brand_tax" value="<?php echo esc_attr( $sel_brand_tax ); ?>">
                <?php endif; ?>
                <?php if ( $resolved_source === 'sale' ) : ?>
                    <input type="hidden" data-param="pf_source" value="sale">
                <?php endif; ?>

                <!-- Category -->
                <details class="al-pf-dd" data-group="pf_cat" role="listbox" aria-multiselectable="true" id="<?php echo esc_attr($cats_dd_id); ?>">
                    <summary class="al-pf-trigger" aria-label="<?php echo esc_attr( $label_category ); ?>" aria-expanded="false" aria-controls="<?php echo esc_attr($cats_dd_id); ?>-panel">
                        <span class="al-pf-labeltxt"><?php echo esc_html( $label_category ); ?></span>
                        <em class="al-pf-summary" data-summary="pf_cat"></em>
                    </summary>
                    <div class="al-pf-panel" id="<?php echo esc_attr($cats_dd_id); ?>-panel" role="group" aria-label="<?php echo esc_attr( $label_category ); ?>">
                        <div class="al-pf-empty" data-empty="pf_cat" <?php echo empty($present['cats']) ? '' : 'hidden aria-hidden="true"'; ?>>
                            <?php echo esc_html( $empty_msg ); ?>
                        </div>
                        <div class="al-pf-menu" <?php echo empty($present['cats']) ? 'hidden aria-hidden="true"' : ''; ?>>
                            <?php if ( ! empty( $present['cats'] ) ) : foreach ( $present['cats'] as $t ) :
                                $slug = $t->slug; $checked = in_array( $slug, $sel_cats, true ); ?>
                                <label class="al-pf-option">
                                    <input type="checkbox" value="<?php echo esc_attr( $slug ); ?>" <?php checked( $checked ); ?> data-param="pf_cat">
                                    <span><?php echo esc_html( $t->name ); ?></span>
                                </label>
                            <?php endforeach; endif; ?>
                        </div>
                    </div>
                </details>

                <!-- Brand -->
                <?php if ( $sel_brand_tax ) : ?>
                    <details class="al-pf-dd" data-group="pf_brand" role="listbox" aria-multiselectable="true" id="<?php echo esc_attr($brand_dd_id); ?>">
                        <summary class="al-pf-trigger" aria-label="<?php echo esc_attr( $label_brand ); ?>" aria-expanded="false" aria-controls="<?php echo esc_attr($brand_dd_id); ?>-panel">
                            <span class="al-pf-labeltxt"><?php echo esc_html( $label_brand ); ?></span>
                            <em class="al-pf-summary" data-summary="pf_brand"></em>
                        </summary>
                        <div class="al-pf-panel" id="<?php echo esc_attr($brand_dd_id); ?>-panel" role="group" aria-label="<?php echo esc_attr( $label_brand ); ?>">
                            <div class="al-pf-empty" data-empty="pf_brand" <?php echo empty($present['brands']) ? '' : 'hidden aria-hidden="true"'; ?>>
                                <?php echo esc_html( $empty_msg ); ?>
                            </div>
                            <div class="al-pf-menu" <?php echo empty($present['brands']) ? 'hidden aria-hidden="true"' : ''; ?>>
                                <?php if ( ! empty( $present['brands'] ) ) : foreach ( $present['brands'] as $b ) :
                                    $slug    = $b->slug;
                                    $checked = in_array( $slug, $sel_brands, true );
                                    $logo    = al_pf_get_term_logo_url( $b->term_id );
                                    ?>
                                    <label class="al-pf-option al-pf-brand-option">
                                        <input type="checkbox" value="<?php echo esc_attr( $slug ); ?>" <?php checked( $checked ); ?> data-param="pf_brand">
                                        <?php if ( $logo ) : ?>
                                            <img class="al-pf-brand-thumb" src="<?php echo esc_url( $logo ); ?>" alt="" loading="lazy" decoding="async">
                                        <?php endif; ?>
                                        <span><?php echo esc_html( $b->name ); ?></span>
                                    </label>
                                <?php endforeach; endif; ?>
                            </div>
                        </div>
                    </details>
                <?php endif; ?>

                <!-- Attributes -->
                <details class="al-pf-dd" data-group="pf_attr_all" role="listbox" aria-multiselectable="true" id="<?php echo esc_attr($attrs_dd_id); ?>">
                    <summary class="al-pf-trigger" aria-label="<?php echo esc_attr( $label_attributes ); ?>" aria-expanded="false" aria-controls="<?php echo esc_attr($attrs_dd_id); ?>-panel">
                        <span class="al-pf-labeltxt"><?php echo esc_html( $label_attributes ); ?></span>
                        <em class="al-pf-summary" data-summary="pf_attr_all"></em>
                    </summary>
                    <div class="al-pf-panel" id="<?php echo esc_attr($attrs_dd_id); ?>-panel" role="group" aria-label="<?php echo esc_attr( $label_attributes ); ?>">
                        <div class="al-pf-empty" data-empty="pf_attr_all" <?php echo ($attr_total_terms===0) ? '' : 'hidden aria-hidden="true"'; ?>>
                            <?php echo esc_html( $empty_msg ); ?>
                        </div>
                        <div class="al-pf-attr-groups" <?php echo ($attr_total_terms===0) ? 'hidden aria-hidden="true"' : ''; ?>>
                            <?php foreach ( $present['attrs'] as $tax => $terms ) :
                                if ( empty( $terms ) ) continue; ?>
                                <section class="al-pf-attr-group" data-attr-tax="<?php echo esc_attr( $tax ); ?>">
                                    <h4 class="al-pf-group-title"><?php echo esc_html( function_exists('wc_attribute_label') ? wc_attribute_label( $tax ) : $tax ); ?></h4>
                                    <div class="al-pf-grid">
                                        <?php foreach ( $terms as $term ) :
                                            $val = $tax . ':' . $term->slug;
                                            $checked = in_array( $val, $sel_attrs, true ); ?>
                                            <label class="al-pf-option">
                                                <input type="checkbox" value="<?php echo esc_attr( $val ); ?>" <?php checked( $checked ); ?> data-param="pf_attr" data-tax="<?php echo esc_attr( $tax ); ?>">
                                                <span><?php echo esc_html( $term->name ); ?></span>
                                            </label>
                                        <?php endforeach; ?>
                                    </div>
                                </section>
                            <?php endforeach; ?>
                        </div>
                    </div>
                </details>

                <?php if ( 'yes' === strtolower( $atts['show_reset'] ) ) : ?>
                    <button type="button" class="al-pf-reset" aria-label="<?php echo esc_attr( $label_reset ); ?>"><?php echo esc_html( $label_reset ); ?></button>
                <?php endif; ?>
            </form>
        </div>

        <style>
            /* Add your CSS if needed */
        </style>

        <script>
            (function(){
                window.__alpfInstances = window.__alpfInstances || [];

                if (!window.CSS || typeof CSS.escape !== 'function') {
                    window.CSS = window.CSS || {};
                    CSS.escape = CSS.escape || function(value){ return String(value).replace(/[^a-zA-Z0-9_\\-]/g, '\\\\$&'); };
                }

                // Simple SR live region for a11y announcements
                var __alpfSR = document.getElementById('alpf-sr');
                if (!__alpfSR) {
                    __alpfSR = document.createElement('div');
                    __alpfSR.id = 'alpf-sr';
                    __alpfSR.setAttribute('aria-live', 'polite');
                    __alpfSR.style.position='absolute'; __alpfSR.style.width='1px'; __alpfSR.style.height='1px';
                    __alpfSR.style.margin='-1px'; __alpfSR.style.border='0'; __alpfSR.style.padding='0';
                    __alpfSR.style.clip='rect(0 0 0 0)'; __alpfSR.style.overflow='hidden';
                    document.body.appendChild(__alpfSR);
                }

                function vw(){ return Math.max(document.documentElement.clientWidth||0, window.innerWidth||0); }
                function vh(){ return Math.max(document.documentElement.clientHeight||0, window.innerHeight||0); }

                document.querySelectorAll('[data-pf]').forEach(function(root){
                    if (root.__alpfInit) return;
                    root.__alpfInit = true;

                    var targetSelector = root.getAttribute('data-target') || '';
                    var exposeAttrsCsv = root.getAttribute('data-expose') || '';
                    var ajaxNonce      = root.getAttribute('data-nonce') || '';
                    var ctxTax         = root.getAttribute('data-ctx-tax') || '';
                    var ctxTermsCsv    = root.getAttribute('data-ctx-terms') || '';
                    var ns             = root.getAttribute('data-ns') || ''; // NAMESPACE
                    var AJAX_URL = '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>';

                    function nsKey(k){ return ns ? (ns + '_' + k) : k; }

                    /* UX helpers */
                    function smoothShow(el){ if(!el) return; el.hidden=false; el.setAttribute('aria-hidden','false'); el.style.opacity='0'; el.style.transform='scale(0.98)'; requestAnimationFrame(function(){ el.style.transition='opacity .18s ease, transform .18s ease'; el.style.opacity='1'; el.style.transform='scale(1)'; }); setTimeout(function(){ el.style.transition=''; el.style.opacity=''; el.style.transform=''; },220); }
                    function smoothHide(el, after){ if(!el||el.hidden){ if(after) after(); return; } if(el.tagName.toLowerCase()==='details') el.removeAttribute('open'); el.style.transition='opacity .18s ease, transform .18s ease'; el.style.opacity='0'; el.style.transform='scale(0.98)'; setTimeout(function(){ el.hidden=true; el.setAttribute('aria-hidden','true'); el.style.transition=''; el.style.opacity=''; el.style.transform=''; if(after) after(); },200); }
                    function removeWithFade(el){ if(!el) return; smoothHide(el, function(){ if(el&&el.parentNode) el.parentNode.removeChild(el); }); }

                    function getCheckedValues(param, scope){
                        var sel=scope||root;
                        var inputs=sel.querySelectorAll('input[type="checkbox"][data-param="'+param+'"]:checked');
                        var vals=[]; inputs.forEach(function(i){ vals.push(i.value); });
                        return vals;
                    }

                    var applyTimer=null; function scheduleApply(){ clearTimeout(applyTimer); applyTimer=setTimeout(function(){ setSummaryText(); applyAjax(true); }, 160); }

                    function setSummaryText(){
                        function t(n){return n>0?'('+n+')':'';}
                        var a=root.querySelector('[data-summary="pf_cat"]');      if(a){a.textContent=t(getCheckedValues('pf_cat').length);}
                        var b=root.querySelector('[data-summary="pf_brand"]');    if(b){b.textContent=t(getCheckedValues('pf_brand').length);}
                        var c=root.querySelector('[data-summary="pf_attr_all"]'); if(c){c.textContent=t(getCheckedValues('pf_attr').length);}
                    }

                    function applyControlsFromUrl(rootEl, url){
                        var params=(url instanceof URL)?url.searchParams:new URL(url||window.location.href).searchParams;
                        function csv(k){ var v=params.get(nsKey(k))||''; return v? v.split(',').map(function(s){ return s.trim(); }).filter(Boolean):[]; }
                        var cats=csv('pf_cat'), brands=csv('pf_brand'), attrs=csv('pf_attr');
                        rootEl.querySelectorAll('input[type="checkbox"][data-param]').forEach(function(cb){ cb.checked=false; });
                        cats.forEach(function(slug){ var cb=rootEl.querySelector('input[type="checkbox"][data-param="pf_cat"][value="'+CSS.escape(slug)+'"]'); if(cb) cb.checked=true; });
                        brands.forEach(function(slug){ var cb=rootEl.querySelector('input[type="checkbox"][data-param="pf_brand"][value="'+CSS.escape(slug)+'"]'); if(cb) cb.checked=true; });
                        attrs.forEach(function(pair){ var cb=rootEl.querySelector('input[type="checkbox"][data-param="pf_attr"][value="'+CSS.escape(pair)+'"]'); if(cb) cb.checked=true; });
                    }

                    function buildUrl(baseHref, opts){
                        opts = opts || {};
                        var url=new URL(baseHref||window.location.href); var params=url.searchParams;

                        root.querySelectorAll('[data-param="s"], [data-param="pf_brand_tax"], [data-param="pf_source"]').forEach(function(h){
                            var key=h.getAttribute('data-param'), val=h.value||'';
                            val ? params.set(nsKey(key), val) : params.delete(nsKey(key));
                        });
                        var cats=getCheckedValues('pf_cat'); cats.length?params.set(nsKey('pf_cat'),cats.join(',')):params.delete(nsKey('pf_cat'));
                        var br=getCheckedValues('pf_brand'); br.length?params.set(nsKey('pf_brand'),br.join(',')):params.delete(nsKey('pf_brand'));
                        var at=getCheckedValues('pf_attr'); at.length?params.set(nsKey('pf_attr'),at.join(',')):params.delete(nsKey('pf_attr'));

                        // Keep orderby if present (namespaced as well)
                        var curr=new URL(window.location.href);
                        var ob = curr.searchParams.get(nsKey('orderby'));
                        if(!params.has(nsKey('orderby')) && ob) params.set(nsKey('orderby'), ob);

                        if (opts.resetPage) {
                            ['paged','page','product-page'].forEach(function(k){ params.delete(k); });
                        }
                        url.search=params.toString(); return url;
                    }

                    // ===== Robust widget + container detection =====
                    function findTargetPair(doc){
                        var d = doc || document;
                        var wrapper = null, container = null;

                        var widgetSelectors = [
                            '.elementor-widget-loop-grid',
                            '.elementor-widget-wc-archive-products',
                            '[data-widget_type*="loop-grid"]',
                            '[data-element_type="widget"][class*="loop-grid"]'
                        ];
                        var containerSelectors = [
                            '.elementor-loop-container',
                            '.e-loop-container',
                            'ul.products',
                            '.woocommerce ul.products',
                            '.elementor-widget-wc-archive-products .products',
                            '.woocommerce-products',
                            '.products',
                            '.woocommerce .products'
                        ];

                        function first(scope, sels){
                            for (var i=0;i<sels.length;i++){
                                var m = scope.querySelector ? scope.querySelector(sels[i]) : null;
                                if (m) return m;
                            }
                            return null;
                        }

                        if (targetSelector) {
                            var t = d.querySelector(targetSelector);
                            if (t) {
                                if (t.matches && (t.matches(widgetSelectors.join(',')))) {
                                    wrapper = t;
                                } else {
                                    wrapper = first(t, widgetSelectors) || null;
                                }
                                if (wrapper) {
                                    container = first(wrapper, containerSelectors) || wrapper.querySelector('ul.products');
                                } else {
                                    container = first(t, containerSelectors);
                                }
                            }
                        }

                        if (!wrapper) wrapper = first(d, widgetSelectors);
                        if (!container) {
                            if (wrapper) container = first(wrapper, containerSelectors);
                            if (!container) container = first(d, containerSelectors);
                        }
                        return { wrapper: wrapper || null, container: container || null };
                    }

                    function setBusy(hostEl, busy){
                        if (!hostEl) return;
                        if (!hostEl.classList.contains('alpf-overlay-host')) {
                            var cs = getComputedStyle(hostEl);
                            if (cs.position === 'static') hostEl.classList.add('alpf-overlay-host');
                        }
                        if (busy) {
                            if (!hostEl.classList.contains('alpf-target-busy')) {
                                hostEl.classList.add('alpf-target-busy');
                                hostEl.setAttribute('aria-busy','true');
                                var overlay = document.createElement('div');
                                overlay.className = 'alpf-preloader';
                                overlay.setAttribute('role','status');
                                overlay.setAttribute('aria-live','polite');
                                var label = '<?php echo esc_js( __( 'Loading products…', 'al-woo-ele-filter' ) ); ?>';
                                overlay.innerHTML = '<div class="alpf-preloader-inner"><div class="alpf-loader" aria-hidden="true"></div><div class="alpf-label">'+ label +'</div></div>';
                                hostEl.appendChild(overlay);
                                requestAnimationFrame(function(){ overlay.classList.add('alpf-show'); });
                            }
                        } else {
                            hostEl.classList.remove('alpf-target-busy');
                            hostEl.removeAttribute('aria-busy');
                            var overlay = hostEl.querySelector('.alpf-preloader');
                            if (overlay) { overlay.classList.remove('alpf-show'); setTimeout(function(){ overlay && overlay.remove(); }, 180); }
                        }
                    }

                    function rehydrateScripts(scope){
                        if(!scope) return;
                        var scripts = scope.querySelectorAll('script');
                        scripts.forEach(function(oldS){
                            var s = document.createElement('script');
                            for (var i=0;i<oldS.attributes.length;i++) { var a = oldS.attributes[i]; s.setAttribute(a.name, a.value); }
                            s.textContent = oldS.textContent || '';
                            oldS.parentNode.replaceChild(s, oldS);
                        });
                    }
                    function rehydrateElementor(scope){
                        if (window.elementorFrontend) {
                            try {
                                var $scope = window.jQuery ? window.jQuery(scope) : null;
                                if (elementorFrontend.elementsHandler && elementorFrontend.elementsHandler.runReadyTrigger) elementorFrontend.elementsHandler.runReadyTrigger($scope || scope);
                                if (elementorFrontend.hooks && elementorFrontend.hooks.doAction) {
                                    elementorFrontend.hooks.doAction('frontend/element_ready/global', $scope || scope);
                                    elementorFrontend.hooks.doAction('frontend/element_ready/loop-grid.default', $scope || scope);
                                    elementorFrontend.hooks.doAction('frontend/element_ready/woocommerce-archive-products.default', $scope || scope);
                                }
                                if (elementorFrontend.elements && elementorFrontend.elements.$window) elementorFrontend.elements.$window.trigger('resize');
                            } catch(e){ console.warn('[al-pf] Elementor re-init failed:', e); }
                        }
                    }

                    function setEmptyState(groupKey, hasItems) {
                        var panel = root.querySelector('[data-group="'+groupKey+'"] .al-pf-panel');
                        if (!panel) return;
                        var emptyEl = panel.querySelector('.al-pf-empty[data-empty="'+groupKey+'"]');
                        var listEl  = panel.querySelector(groupKey === 'pf_attr_all' ? '.al-pf-attr-groups' : '.al-pf-menu');
                        if (emptyEl) {
                            emptyEl.hidden = !!hasItems;
                            emptyEl.setAttribute('aria-hidden', hasItems ? 'true' : 'false');
                        }
                        if (listEl) {
                            listEl.hidden = !hasItems;
                            listEl.setAttribute('aria-hidden', hasItems ? 'false' : 'true');
                        }
                    }

                    function rebuildCheckboxList(container, param, items) {
                        var selected = new Set(getCheckedValues(param));
                        container.innerHTML = '';
                        var hasItems = !!(items && items.length);

                        if (!hasItems) {
                            setEmptyState(param, false);
                            return;
                        }

                        items.forEach(function(it){
                            var label = document.createElement('label');
                            label.className = 'al-pf-option' + (param === 'pf_brand' ? ' al-pf-brand-option' : '');

                            var input = document.createElement('input');
                            input.type = 'checkbox';
                            input.setAttribute('data-param', param);
                            input.value = it.slug;
                            if (selected.has(it.slug)) input.checked = true;
                            input.addEventListener('change', scheduleApply);
                            label.appendChild(input);

                            if (param === 'pf_brand' && it.logo) {
                                var img = document.createElement('img');
                                img.className = 'al-pf-brand-thumb';
                                img.src = it.logo;
                                img.alt = '';
                                img.loading = 'lazy';
                                img.decoding = 'async';
                                label.appendChild(img);
                            }

                            var span = document.createElement('span');
                            span.textContent = it.name;
                            label.appendChild(span);

                            container.appendChild(label);
                        });

                        setEmptyState(param, true);
                    }

                    async function refreshFilterOptions(urlObj){
                        var u = new URL(AJAX_URL, window.location.origin);
                        var activeUrl = (urlObj instanceof URL) ? urlObj : new URL(window.location.href);
                        u.search = activeUrl.search;
                        u.searchParams.set('action','alpf_terms');
                        if (exposeAttrsCsv) u.searchParams.set('expose_attrs', exposeAttrsCsv);
                        if (ajaxNonce) u.searchParams.set('security', ajaxNonce);
                        if (ctxTax) u.searchParams.set('ctx_tax', ctxTax);
                        if (ctxTermsCsv) u.searchParams.set('ctx_terms', ctxTermsCsv);
                        if (ns) u.searchParams.set('ns', ns); // pass namespace to server

                        try{
                            var res = await fetch(u.toString(), { credentials:'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } });
                            if (!res.ok) throw new Error('HTTP '+res.status);
                            var j = await res.json();
                            if (!j || !j.success) return;

                            var data = j.data || {};
                            var attrLabels = data.attr_labels || {};

                            var catDetails = root.querySelector('[data-group="pf_cat"]');
                            var catMenu    = root.querySelector('[data-group="pf_cat"] .al-pf-menu');
                            if (catDetails && catMenu) {
                                rebuildCheckboxList(catMenu, 'pf_cat', data.cats || []);
                            }

                            var brandDetails = root.querySelector('[data-group="pf_brand"]');
                            var brandMenu    = root.querySelector('[data-group="pf_brand"] .al-pf-menu');
                            if (brandDetails && brandMenu) {
                                rebuildCheckboxList(brandMenu, 'pf_brand', data.brands || []);
                            }

                            // Remove empty attribute sections; we still keep the dropdown visible with empty message
                            Array.from(root.querySelectorAll('.al-pf-attr-group')).forEach(function(sec){
                                var tax = sec.getAttribute('data-attr-tax') || '';
                                var items = (data.attrs && data.attrs[tax]) ? data.attrs[tax] : [];
                                if (!items.length) removeWithFade(sec);
                            });

                            function ensureAttrSection(tax, labelText){
                                var groupsWrap = root.querySelector('.al-pf-attr-groups');
                                if (!groupsWrap) return null;
                                var section = root.querySelector('.al-pf-attr-group[data-attr-tax="'+CSS.escape(tax)+'"]');
                                if (section) return section;
                                section = document.createElement('section');
                                section.className = 'al-pf-attr-group';
                                section.setAttribute('data-attr-tax', tax);
                                var h4 = document.createElement('h4'); h4.className = 'al-pf-group-title'; h4.textContent = labelText || tax;
                                var grid = document.createElement('div'); grid.className = 'al-pf-grid';
                                section.appendChild(h4); section.appendChild(grid);
                                groupsWrap.appendChild(section);
                                return section;
                            }

                            function rebuildAttrGroup(section, tax, items){
                                var grid = section.querySelector('.al-pf-grid');
                                var selected = new Set(getCheckedValues('pf_attr'));
                                grid.innerHTML = '';
                                if (!items || !items.length) return;
                                items.forEach(function(it){
                                    var val = tax + ':' + it.slug;
                                    var label = document.createElement('label'); label.className='al-pf-option';
                                    var input = document.createElement('input');
                                    input.type='checkbox'; input.setAttribute('data-param','pf_attr'); input.setAttribute('data-tax',tax); input.value = val;
                                    if (selected.has(val)) input.checked = true;
                                    input.addEventListener('change', scheduleApply);
                                    var span = document.createElement('span'); span.textContent = it.name;
                                    label.appendChild(input); label.appendChild(span);
                                    grid.appendChild(label);
                                });
                            }

                            (Object.keys(data.attrs||{})).forEach(function(tax){
                                var items = (data.attrs && data.attrs[tax]) ? data.attrs[tax] : [];
                                if (!items.length) return;
                                var labelText = (attrLabels && attrLabels[tax]) ? attrLabels[tax] : tax;
                                var section = ensureAttrSection(tax, labelText);
                                if (!section) return;
                                rebuildAttrGroup(section, tax, items);
                                section.hidden=false; section.setAttribute('aria-hidden','false');
                                section.style.opacity='0'; section.style.transform='scale(0.98)';
                                requestAnimationFrame(function(){
                                    section.style.transition='opacity .18s ease, transform .18s ease';
                                    section.style.opacity='1'; section.style.transform='scale(1)';
                                    setTimeout(function(){ section.style.transition=''; section.style.opacity=''; section.style.transform=''; },220);
                                });
                            });

                            var anyAttrItems = data.attrs && Object.keys(data.attrs).some(function(t){ return (data.attrs[t]||[]).length; });
                            setEmptyState('pf_attr_all', !!anyAttrItems);

                            setSummaryText();
                        } catch(err){ console.warn('[al-pf] terms refresh failed:', err); }
                    }

                    async function fetchAndSwap(url){
                        var cur = findTargetPair(document);
                        var busyHost = cur.wrapper || cur.container;
                        if (!busyHost) { window.location.href = url.toString(); return; }

                        setBusy(busyHost, true);
                        var safetyTimer = setTimeout(function(){ setBusy(busyHost, false); }, 8000);

                        try {
                            var xhrUrl = new URL(url.toString());
                            xhrUrl.searchParams.set('_alpf', '1');

                            var res = await fetch(xhrUrl.toString(), {
                                credentials:'same-origin',
                                headers: { 'X-Requested-With': 'XMLHttpRequest' }
                            });
                            if (!res.ok) throw new Error('HTTP '+res.status);
                            var html = await res.text();
                            var parser = new DOMParser();
                            var doc = parser.parseFromString(html, 'text/html');

                            var neu = findTargetPair(doc);
                            var newWrapper = neu.wrapper;
                            var newContainer = neu.container;

                            if (!newWrapper && !newContainer) throw new Error('Target nodes not found in response');

                            var didReplaceWrapper = false;
                            if (cur.wrapper && newWrapper) {
                                var tmp = document.createElement('div');
                                tmp.innerHTML = newWrapper.outerHTML;
                                var fresh = tmp.firstElementChild;
                                cur.wrapper.replaceWith(fresh);
                                var updated = findTargetPair(document);
                                didReplaceWrapper = true;
                                rehydrateScripts(updated.wrapper || updated.container);
                                rehydrateElementor(updated.wrapper || updated.container);
                                bindContainerInteractions(updated.container || updated.wrapper);
                                setBusy(updated.wrapper || updated.container, false);
                            } else if (cur.container && newContainer) {
                                cur.container.innerHTML = newContainer.innerHTML;
                                rehydrateScripts(cur.container);
                                rehydrateElementor(cur.container);
                                bindContainerInteractions(cur.container);
                            } else {
                                window.location.href = url.toString();
                                return;
                            }

                            refreshFilterOptions(url);

                            var region = (didReplaceWrapper ? (findTargetPair(document).wrapper || findTargetPair(document).container) : cur.container);
                            if (region) {
                                region.setAttribute('role','region');
                                region.setAttribute('aria-live','polite');
                                region.scrollIntoView({ behavior:'smooth', block:'start' });
                            }

                            __alpfSR.textContent = '<?php echo esc_js( __( 'Products updated', 'al-woo-ele-filter' ) ); ?>';

                            document.dispatchEvent(new CustomEvent('alpf:afterSwap', { detail: { container: region, url: url.toString() } }));
                            if (window.jQuery) {
                                jQuery(document.body).trigger('alpf_after_swap', [ region, url.toString() ]);
                                jQuery(document.body).trigger('updated_wc_div');
                                jQuery(document.body).trigger('wc_fragment_refresh');
                                jQuery(document.body).trigger('init_price_filter');
                                jQuery(document.body).trigger('wc_update_cart');
                            }
                        } catch(err){
                            console.error('[al-pf] AJAX swap failed:', err);
                            window.location.href = url.toString();
                        } finally {
                            clearTimeout(safetyTimer);
                            var latest = findTargetPair(document);
                            setBusy(latest.wrapper || latest.container || busyHost, false);
                        }
                    }

                    function applyAjax(filtersChanged){
                        var url = buildUrl(undefined, { resetPage: !!filtersChanged });
                        var href = url.toString();
                        if (href !== window.location.href) {
                            history.pushState({ alpf: 1, href: href }, '', href);
                        }
                        fetchAndSwap(url);
                        if (filtersChanged) closeAll(null);
                    }

                    // Close all dropdowns except the provided one
                    function closeAll(except){
                        root.querySelectorAll('.al-pf-dd[open]').forEach(function(dd){ if (dd !== except) dd.removeAttribute('open'); });
                    }

                    // ===== Mobile viewport clamping / gap fix (no overlay) =====
                    function fitPanel(dd){
                        if (!dd) return;
                        var panel = dd.querySelector('.al-pf-panel'); if (!panel) return;

                        // Reset positioning
                        panel.classList.remove('mobile-fit');
                        panel.style.position=''; panel.style.left=''; panel.style.right=''; panel.style.top=''; panel.style.bottom='';
                        panel.style.maxHeight=''; panel.style.width=''; panel.style.maxWidth=''; panel.classList.remove('fit-clamped');
                        dd.classList.remove('flip');

                        var viewportW = vw();
                        var viewportH = vh();
                        var GAP = 10; // ensure there's always a visual gap from the opener

                        // Small screens: fixed sheet without overlay; clamp and add gap so it never overlaps opener
                        if (viewportW <= 480) {
                            var ddRect = dd.getBoundingClientRect();
                            panel.classList.add('mobile-fit');
                            panel.style.left = '8px';
                            panel.style.right = '8px';
                            panel.style.maxHeight = Math.round(viewportH * 0.7) + 'px';

                            // Measure current height (after maxHeight applied)
                            var ph = panel.getBoundingClientRect().height || Math.round(viewportH * 0.7);

                            var spaceBelow = viewportH - ddRect.bottom - GAP;
                            var spaceAbove = ddRect.top - GAP;

                            if (spaceBelow >= Math.min(ph, viewportH * 0.4)) {
                                // Open below, with gap -> no overlap with opener
                                var top = Math.min(ddRect.bottom + GAP, viewportH - GAP - Math.min(ph, Math.round(viewportH * 0.7)));
                                panel.style.top = top + 'px';
                            } else {
                                // Open above, with gap
                                var topAbove = Math.max(GAP, ddRect.top - GAP - Math.min(ph, Math.round(viewportH * 0.7)));
                                panel.style.top = topAbove + 'px';
                            }
                            return;
                        }

                        // Desktop/tablet: absolute positioning with clamping and flip
                        var vwPadding = 12;
                        var maxW = Math.min(1200, viewportW - (vwPadding*2));
                        var natural = Math.ceil(panel.scrollWidth + 20);
                        var minW = Math.max(220, Math.min(340, viewportW - 2*vwPadding));
                        var finalW = Math.max(minW, Math.min(natural, maxW));
                        panel.style.maxWidth = Math.round(maxW) + 'px';
                        panel.style.width = Math.round(finalW) + 'px';

                        panel.style.left='0px'; panel.style.right='auto';
                        var rect=panel.getBoundingClientRect();
                        if (rect.right > viewportW - vwPadding) { panel.style.left='auto'; panel.style.right='0px'; }

                        var ddRect=dd.getBoundingClientRect(); var spaceBelow=viewportH-ddRect.bottom; var spaceAbove=ddRect.top;
                        if (spaceBelow < rect.height && spaceAbove > spaceBelow) dd.classList.add('flip');

                        // After flip, re-measure and correct horizontal overflow
                        rect = panel.getBoundingClientRect();
                        if (rect.left < vwPadding) {
                            panel.style.left = (vwPadding - (ddRect.left)) + 'px';
                            panel.style.right = 'auto';
                        }
                        rect = panel.getBoundingClientRect();
                        if (rect.right > viewportW - vwPadding) {
                            panel.style.left = 'auto';
                            panel.style.right = (vwPadding) + 'px';
                        }
                    }

                    function bindContainerInteractions(container){
                        if (!container) return;
                        if (!container.dataset.alpfPagBound) {
                            container.addEventListener('click', function(e){
                                var a = e.target.closest('.page-numbers a, a.page-numbers, .woocommerce-pagination a');
                                if (!a) return;
                                e.preventDefault();
                                var url = new URL(a.href);
                                var href = url.toString();
                                if (href !== window.location.href) {
                                    history.pushState({ alpf:1, href: href }, '', href);
                                }
                                fetchAndSwap(url);
                            }, { passive:false });
                            container.dataset.alpfPagBound = '1';
                        }

                        var orderForm = container.querySelector('form.woocommerce-ordering');
                        var select    = orderForm ? orderForm.querySelector('select[name="orderby"]') : null;
                        if (!select) {
                            var globalForm = document.querySelector('form.woocommerce-ordering');
                            if (globalForm) select = globalForm.querySelector('select[name="orderby"]');
                        }
                        if (select && !select.dataset.alpfBound) {
                            select.addEventListener('change', function(){
                                var url = buildUrl(undefined, { resetPage: true });
                                url.searchParams.set(nsKey('orderby'), select.value); // namespaced orderby
                                var href = url.toString();
                                if (href !== window.location.href) {
                                    history.pushState({ alpf:1, href: href }, '', href);
                                }
                                fetchAndSwap(url);
                            });
                            select.dataset.alpfBound = '1';
                        }
                    }

                    // Toggle behavior + events (enforce single-open)
                    root.querySelectorAll('.al-pf-dd').forEach(function(dd){
                        var summary = dd.querySelector('.al-pf-trigger');

                        // Pre-close siblings BEFORE the browser toggles (pointer/keyboard)
                        if (summary) {
                            summary.addEventListener('pointerdown', function(){
                                if (!dd.open) closeAll(dd);
                            });
                            summary.addEventListener('keydown', function(e){
                                if (e.key === 'Enter' || e.key === ' ') {
                                    if (!dd.open) closeAll(dd);
                                }
                            });
                        }

                        // Fallback when toggle completes
                        dd.addEventListener('toggle', function(){
                            if (summary) summary.setAttribute('aria-expanded', dd.open ? 'true' : 'false');
                            if (dd.open) {
                                closeAll(dd);
                                fitPanel(dd);
                            }
                        });

                        // Close on ESC
                        dd.addEventListener('keydown', function(e){
                            if (e.key === 'Escape') dd.removeAttribute('open');
                        });
                    });

                    // Refit on resize/orientation/scroll (helps fixed mobile sheet stay aligned)
                    var refit = function(){
                        var openDD = root.querySelector('.al-pf-dd[open]');
                        if (openDD) fitPanel(openDD);
                    };
                    window.addEventListener('resize', refit, { passive:true });
                    window.addEventListener('orientationchange', refit, { passive:true });
                    window.addEventListener('scroll', function(){
                        if (document.querySelector('.al-pf-panel.mobile-fit')) refit();
                    }, { passive:true });

                    document.addEventListener('click', function(e){
                        var isInside = e.target.closest ? e.target.closest('.al-pf-dd') : null;
                        if (isInside && root.contains(isInside)) return;
                        if (!root.contains(e.target)) closeAll(null);
                    });

                    // Change listeners
                    root.querySelectorAll('input[type="checkbox"][data-param]').forEach(function(cb){ cb.addEventListener('change', scheduleApply); });

                    // Reset
                    var resetBtn = root.querySelector('.al-pf-reset');
                    if (resetBtn){
                        resetBtn.addEventListener('click', function(){
                            root.querySelectorAll('input[type="checkbox"][data-param]').forEach(function(cb){ cb.checked = false; });
                            setSummaryText();
                            var url = buildUrl(undefined, { resetPage: true });
                            url.searchParams.delete(nsKey('orderby'));
                            var href = url.toString();
                            if (href !== window.location.href) {
                                history.pushState({ alpf:1, href: href }, '', href);
                            }
                            fetchAndSwap(url);
                            closeAll(null);
                        });
                    }

                    // Initial
                    applyControlsFromUrl(root, window.location.href);
                    var initialPair = findTargetPair(document);
                    bindContainerInteractions(initialPair.container || initialPair.wrapper);
                    setSummaryText();
                    refreshFilterOptions(new URL(window.location.href));

                    if (initialPair.container || initialPair.wrapper) {
                        var scope = initialPair.wrapper || initialPair.container;
                        document.dispatchEvent(new CustomEvent('alpf:afterSwap', { detail: { container: scope, url: window.location.href } }));
                        if (window.jQuery) jQuery(document.body).trigger('alpf_after_swap', [ scope, window.location.href ]);
                    }

                    window.__alpfInstances.push({ root: root, refresh: function(){ var url = new URL(window.location.href); fetchAndSwap(url); } });
                });

                // History back/forward — honor namespace
                window.addEventListener('popstate', function(){
                    if (!window.__alpfInstances) return;
                    var url = new URL(window.location.href);
                    window.__alpfInstances.forEach(function(i){
                        if (!i || !i.root) return;
                        var root=i.root, ns=root.getAttribute('data-ns')||'';
                        function nsKey(k){ return ns ? (ns+'_'+k) : k; }
                        function csv(k){ var v=url.searchParams.get(nsKey(k))||''; return v? v.split(',').map(function(s){return s.trim();}).filter(Boolean):[]; }
                        var cats=csv('pf_cat'), brands=csv('pf_brand'), attrs=csv('pf_attr');
                        root.querySelectorAll('input[type="checkbox"][data-param]').forEach(function(cb){ cb.checked=false; });
                        cats.forEach(function(slug){ var cb=root.querySelector('input[type="checkbox"][data-param="pf_cat"][value="'+CSS.escape(slug)+'"]'); if(cb) cb.checked=true; });
                        brands.forEach(function(slug){ var cb=root.querySelector('input[type="checkbox"][data-param="pf_brand"][value="'+CSS.escape(slug)+'"]'); if(cb) cb.checked=true; });
                        attrs.forEach(function(pair){ var cb=root.querySelector('input[type="checkbox"][data-param="pf_attr"][value="'+CSS.escape(pair)+'"]'); if(cb) cb.checked=true; });
                        function gv(p){ return Array.from(root.querySelectorAll('input[type="checkbox"][data-param="'+p+'"]:checked')).map(function(n){return n.value;}); }
                        function t(n){return n>0?'('+n+')':'';}
                        var a=root.querySelector('[data-summary="pf_cat"]');      if(a){a.textContent=t(gv('pf_cat').length);}
                        var b=root.querySelector('[data-summary="pf_brand"]');    if(b){b.textContent=t(gv('pf_brand').length);}
                        var c=root.querySelector('[data-summary="pf_attr_all"]'); if(c){c.textContent=t(gv('pf_attr').length);}
                        if (typeof i.refresh === 'function') i.refresh();
                    });
                });
            })();
        </script>
        <?php
        return ob_get_clean();
    }
}
add_shortcode( 'wc_filter_bar', 'al_pf_filter_bar_shortcode' );

/** =========================
 *  Server-side query filters (main query)
 *  ========================= */
if ( ! function_exists( 'al_pf_filter_main_search_query' ) ) {
    function al_pf_filter_main_search_query( $q ) {
        if ( is_admin() || ! ( $q instanceof WP_Query ) || ! $q->is_main_query() ) return;

        // Detect any namespaced or non-namespaced PF params
        $has_pf_params = alpf_request_has_pf_params_globally();

        $is_product_archive = function_exists('is_post_type_archive') && is_post_type_archive('product');
        $is_product_tax     = false;
        if ( function_exists('get_object_taxonomies') && ! is_null( get_object_taxonomies( 'product' ) ) ) {
            foreach ( get_object_taxonomies( 'product' ) as $ptax ) {
                if ( function_exists('is_tax') && is_tax( $ptax ) ) { $is_product_tax = true; break; }
            }
        }
        $is_product_pt = ( 'product' === $q->get('post_type') || ( is_array( $q->get('post_type') ) && in_array( 'product', (array) $q->get('post_type'), true ) ) );

        if ( $q->is_search() && ! $has_pf_params && ! $is_product_archive && ! $is_product_tax && ! $is_product_pt ) return;

        if ( $q->is_search() && $has_pf_params ) {
            $q->set( 'post_type', array( 'product' ) );
        }

        if ( $has_pf_params || $is_product_archive || $is_product_tax || $is_product_pt ) {
            $parts = al_pf_build_tax_query_parts( '', '' );
            if ( $parts ) {
                $tax_query = (array) $q->get( 'tax_query' );
                $tax_query = array_merge( $tax_query, $parts );
                if ( count( $tax_query ) > 1 && ! isset( $tax_query['relation'] ) ) {
                    $tax_query = array_merge( array( 'relation' => 'AND' ), $tax_query );
                }
                $q->set( 'tax_query', $tax_query );
            }

            list( $guard_tax, $guard_meta ) = alpf_build_catalog_guards( $q->is_search(), '' );
            $tax_query  = array_merge( (array) $q->get('tax_query'),  $guard_tax );
            $meta_query = array_merge( (array) $q->get('meta_query'), $guard_meta );

            if ( count( $tax_query ) > 1 && ! isset( $tax_query['relation'] ) ) {
                $tax_query = array_merge( array( 'relation' => 'AND' ), $tax_query );
            }
            if ( count( $meta_query ) > 1 && ! isset( $meta_query['relation'] ) ) {
                $meta_query = array_merge( array( 'relation' => 'AND' ), $meta_query );
            }

            // Apply effective source globally (ns '')
            $args = al_pf_apply_source_to_args( array(), '' );
            if ( ! empty( $args['post__in'] ) ) {
                $existing = (array) $q->get( 'post__in' );
                if ( ! empty( $existing ) ) {
                    $q->set( 'post__in', array_values( array_intersect( array_map( 'intval', $existing ), array_map( 'intval', $args['post__in'] ) ) ) );
                } else {
                    $q->set( 'post__in', $args['post__in'] );
                }
            }

            $q->set( 'tax_query',  $tax_query );
            $q->set( 'meta_query', $meta_query );
        }
    }
}
add_action( 'pre_get_posts', 'al_pf_filter_main_search_query' );

/** =========================
 *  Elementor Loop Grid query — namespaced
 *  ========================= */
if ( ! function_exists( 'al_pf_elementor_posts_query' ) ) {
    function al_pf_elementor_posts_query( $query ) {
        if ( is_admin() ) return;
        if ( ! ( $query instanceof WP_Query ) ) return;

        $hook = current_filter();
        $parts = explode( '/', (string) $hook );
        $ns = sanitize_key( end( $parts ) );

        $default_pp = (int) apply_filters( 'alpf_posts_per_page_default', 30 );
        $pp = (int) $query->get( 'posts_per_page' );
        if ( $pp < 1 ) $query->set( 'posts_per_page', $default_pp );

        // namespaced orderby
        $orderby_raw = alpf_get_param( 'orderby', $ns );
        if ( $orderby_raw !== null && $orderby_raw !== '' ) {
            $orderby = function_exists('wc_clean') ? wc_clean( wp_unslash( $orderby_raw ) ) : sanitize_text_field( wp_unslash( $orderby_raw ) );
            al_pf_apply_elementor_orderby_map( $query, $orderby );
        }

        // Any namespaced filter/search or effective source default?
        $effective_source = alpf_get_effective_source( $ns );
        $has_params = (
            alpf_get_param( 'pf_cat', $ns ) !== null ||
            alpf_get_param( 'pf_brand', $ns ) !== null ||
            alpf_get_param( 'pf_attr', $ns ) !== null ||
            alpf_get_param( 'pf_brand_tax', $ns ) !== null ||
            alpf_get_param( 's', $ns ) !== null ||
            alpf_get_param( 'min_price', $ns ) !== null ||
            alpf_get_param( 'max_price', $ns ) !== null ||
            $effective_source !== ''
        );

        if ( ! $has_params ) return;

        $pt = $query->get('post_type');
        if ( $pt && $pt !== 'product' && ( ! is_array($pt) || ! in_array( 'product', (array) $pt, true ) ) ) return;

        $parts = al_pf_build_tax_query_parts( '', $ns );
        if ( $parts ) {
            $tax_query = (array) $query->get( 'tax_query' );
            $tax_query = array_merge( $tax_query, $parts );
            if ( count( $tax_query ) > 1 && ! isset( $tax_query['relation'] ) ) {
                $tax_query = array_merge( array( 'relation' => 'AND' ), $tax_query );
            }
            $query->set( 'tax_query', $tax_query );
        }

        $s_raw = alpf_get_param( 's', $ns );
        if ( $s_raw !== null && $s_raw !== '' ) {
            $query->set( 'post_type', array( 'product' ) );
            $query->set( 's', sanitize_text_field( wp_unslash( $s_raw ) ) );
        }

        list( $guard_tax, $guard_meta ) = alpf_build_catalog_guards( $s_raw !== null && $s_raw !== '', $ns );
        $tax_query  = array_merge( (array) $query->get('tax_query'),  $guard_tax );
        $meta_query = array_merge( (array) $query->get('meta_query'), $guard_meta );

        if ( count( $tax_query ) > 1 && ! isset( $tax_query['relation'] ) ) {
            $tax_query = array_merge( array( 'relation' => 'AND' ), $tax_query );
        }
        if ( count( $meta_query ) > 1 && ! isset( $meta_query['relation'] ) ) {
            $meta_query = array_merge( array( 'relation' => 'AND' ), $meta_query );
        }

        // Apply effective source (e.g., sale) for this namespace, intersect with existing post__in
        $args = al_pf_apply_source_to_args( array(), $ns );
        if ( ! empty( $args['post__in'] ) ) {
            $existing = (array) $query->get( 'post__in' );
            if ( ! empty( $existing ) ) {
                $query->set( 'post__in', array_values( array_intersect( array_map( 'intval', $existing ), array_map( 'intval', $args['post__in'] ) ) ) );
            } else {
                $query->set( 'post__in', $args['post__in'] );
            }
        }

        $query->set( 'tax_query',  $tax_query );
        $query->set( 'meta_query', $meta_query );
    }
}

/** Woo 'orderby' map */
if ( ! function_exists( 'al_pf_apply_elementor_orderby_map' ) ) {
    function al_pf_apply_elementor_orderby_map( WP_Query $query, $orderby ) {
        switch ( $orderby ) {
            case 'price':
                $query->set( 'meta_key', '_price' ); $query->set( 'orderby', 'meta_value_num' ); $query->set( 'order', 'ASC' ); break;
            case 'price-desc':
                $query->set( 'meta_key', '_price' ); $query->set( 'orderby', 'meta_value_num' ); $query->set( 'order', 'DESC' ); break;
            case 'popularity':
                $query->set( 'meta_key', 'total_sales' ); $query->set( 'orderby', 'meta_value_num' ); $query->set( 'order', 'DESC' ); break;
            case 'rating':
                $query->set( 'meta_key', '_wc_average_rating' ); $query->set( 'orderby', 'meta_value_num' ); $query->set( 'order', 'DESC' ); break;
            case 'date':
                $query->set( 'orderby', 'date' ); $query->set( 'order', 'DESC' ); break;
            case 'menu_order':
            case 'default':
            default:
                $query->set( 'orderby', 'menu_order title' ); $query->set( 'order', 'ASC' ); break;
        }
    }
}

/** =========================
 *  AJAX: terms (namespaced)
 *  ========================= */
if ( ! function_exists( 'al_pf_ajax_terms' ) ) {
    function al_pf_ajax_terms() {
        if ( empty( $_REQUEST['security'] ) || ! wp_verify_nonce( $_REQUEST['security'], 'alpf_terms' ) ) {
            wp_send_json_error( array( 'message' => 'Invalid nonce' ), 403 );
        }

        $ns = ! empty( $_REQUEST['ns'] ) ? sanitize_key( wp_unslash( $_REQUEST['ns'] ) ) : '';

        $brand_tax = '';
        $brand_tax_req = alpf_get_param( 'pf_brand_tax', $ns );
        if ( ! empty( $brand_tax_req ) ) {
            $brand_tax = sanitize_key( wp_unslash( $brand_tax_req ) );
            if ( ! taxonomy_exists( $brand_tax ) ) $brand_tax = '';
        }
        if ( ! $brand_tax ) $brand_tax = al_pf_get_brand_taxonomy();

        $attr_tax_slugs = array();
        if ( ! empty( $_REQUEST['expose_attrs'] ) ) {
            foreach ( explode( ',', (string) wp_unslash( $_REQUEST['expose_attrs'] ) ) as $maybe ) {
                $tax = sanitize_key( trim( $maybe ) );
                if ( $tax && taxonomy_exists( $tax ) ) $attr_tax_slugs[] = $tax;
            }
        } else if ( function_exists( 'wc_get_attribute_taxonomies' ) ) {
            foreach ( wc_get_attribute_taxonomies() as $ga ) {
                $tax = 'pa_' . $ga->attribute_name;
                if ( taxonomy_exists( $tax ) ) $attr_tax_slugs[] = $tax;
            }
        }

        $ctx_tax   = ! empty( $_REQUEST['ctx_tax'] ) ? sanitize_key( wp_unslash( $_REQUEST['ctx_tax'] ) ) : '';
        $ctx_terms = array();
        if ( ! empty( $_REQUEST['ctx_terms'] ) ) {
            $ctx_terms = array_filter( array_map( 'sanitize_title', array_map( 'trim', explode( ',', (string) wp_unslash( $_REQUEST['ctx_terms'] ) ) ) ) );
        }

        $terms = al_pf_collect_terms_for_filters( $brand_tax, $attr_tax_slugs, array(
            'tax'   => $ctx_tax,
            'terms' => $ctx_terms,
        ), $ns );

        $out = array(
            'cats'        => array(),
            'brands'      => array(),
            'attrs'       => array(),
            'attr_labels' => array(),
            'brand_tax'   => $brand_tax,
        );

        foreach ( (array) $terms['cats'] as $t ) {
            $out['cats'][]   = array( 'slug'=>$t->slug, 'name'=>$t->name );
        }
        foreach ( (array) $terms['brands'] as $b ) {
            $out['brands'][] = array(
                'slug' => $b->slug,
                'name' => $b->name,
                'logo' => al_pf_get_term_logo_url( $b->term_id ),
            );
        }
        foreach ( (array) $terms['attrs'] as $tax => $arr ) {
            $group = array();
            foreach ( (array) $arr as $term ) {
                $group[] = array( 'slug'=>$term->slug, 'name'=>$term->name, 'tax'=>$tax );
            }
            $out['attrs'][ $tax ] = $group;
        }

        $all_attr_taxes = $attr_tax_slugs ? $attr_tax_slugs : array_keys( (array) $terms['attrs'] );
        foreach ( $all_attr_taxes as $tax ) {
            $out['attr_labels'][ $tax ] = function_exists('wc_attribute_label') ? wc_attribute_label( $tax ) : $tax;
        }

        nocache_headers();
        wp_send_json_success( $out );
    }
}
add_action( 'wp_ajax_nopriv_alpf_terms', 'al_pf_ajax_terms' );
add_action( 'wp_ajax_alpf_terms',        'al_pf_ajax_terms' );

/** =========================
 *  Plugin defaults / developer hooks
 *  =========================
 * Ensure first-paint sale filtering for the common namespace "sale_query_id".
 */
add_filter( 'alpf_default_source_map', function( $map ){
    if ( ! is_array( $map ) ) $map = array();
    if ( empty( $map['sale_query_id'] ) ) {
        $map['sale_query_id'] = 'sale';
    }
    return $map;
}, 5 );

/** Cache key extra — language/currency awareness (optional but helpful) */
add_filter( 'alpf_transient_key_extra', function( $extra ) {
    $lang = defined( 'ICL_LANGUAGE_CODE' ) ? ICL_LANGUAGE_CODE : ( function_exists( 'pll_current_language' ) ? pll_current_language() : '' );
    $currency = function_exists( 'get_woocommerce_currency' ) ? get_woocommerce_currency() : '';
    return trim( $extra . '|' . $lang . '|' . $currency, '|' );
}, 5 );

/** Index + bust term scan caches when catalog changes */
if ( ! function_exists( 'alpf_index_transient' ) ) {
    function alpf_index_transient( $key ) {
        $idx = get_option( 'alpf_terms_keys', array() );
        if ( ! in_array( $key, $idx, true ) ) {
            $idx[] = $key;
            update_option( 'alpf_terms_keys', $idx, false );
        }
    }
}
if ( ! function_exists( 'alpf_clear_terms_cache' ) ) {
    function alpf_clear_terms_cache() {
        $idx = (array) get_option( 'alpf_terms_keys', array() );
        foreach ( $idx as $k ) { delete_transient( $k ); }
        update_option( 'alpf_terms_keys', array(), false );
        delete_transient( 'alpf_sale_ids_v1' );
    }
}
add_action( 'save_post_product', 'alpf_clear_terms_cache' );
add_action( 'created_term', 'alpf_clear_terms_cache', 10, 3 );
add_action( 'edited_term',  'alpf_clear_terms_cache', 10, 3 );
add_action( 'delete_term',  'alpf_clear_terms_cache', 10, 4 );


/**
 * Plugin Name: WooCommerce – YouTube Video URL Product Field
 * Description: Adds a "YouTube Video URL" custom field to WooCommerce products (General tab), and shows a play icon over the product gallery to open the video in a popup.
 * Author: Your Name
 * Version: 1.1.0
 * License: GPL-2.0+
 */

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

class WC_YouTube_URL_Field {
    const META_KEY = '_youtube_video_url';

    public function __construct() {
        // Admin: field render + save
        add_action( 'woocommerce_product_options_general_product_data', [ $this, 'render_field' ] );
        add_action( 'woocommerce_admin_process_product_object',        [ $this, 'save_field_object' ] );
        add_action( 'woocommerce_process_product_meta',               [ $this, 'save_field_meta' ] );

        // Frontend: enqueue & add play overlay in single product gallery
        add_action( 'wp_enqueue_scripts', [ $this, 'maybe_enqueue_frontend_assets' ] );
        add_action( 'woocommerce_product_thumbnails', [ $this, 'output_play_icon_overlay' ], 5 );
    }

    /* ---------------------------
     * Admin field (General tab)
     * --------------------------*/
    public function render_field() {
        echo '<div class="options_group">';
        woocommerce_wp_text_input( [
            'id'          => self::META_KEY,
            'label'       => __( 'YouTube Video URL', 'woocommerce' ),
            'placeholder' => 'https://www.youtube.com/watch?v=VIDEO_ID',
            'desc_tip'    => true,
            'description' => __( 'Paste a full YouTube URL (watch, youtu.be, or shorts).', 'woocommerce' ),
            'type'        => 'url',
        ] );
        echo '</div>';
    }

    public function save_field_object( $product ) {
        if ( ! current_user_can( 'manage_woocommerce' ) ) return;

        if ( isset( $_POST[ self::META_KEY ] ) ) {
            $url = trim( wp_unslash( $_POST[ self::META_KEY ] ) );
            $product->update_meta_data( self::META_KEY, $url ? esc_url_raw( $url ) : '' );
        }
    }

    public function save_field_meta( $post_id ) {
        if ( ! current_user_can( 'manage_woocommerce' ) ) return;

        if ( isset( $_POST[ self::META_KEY ] ) ) {
            $url = trim( wp_unslash( $_POST[ self::META_KEY ] ) );
            update_post_meta( $post_id, self::META_KEY, $url ? esc_url_raw( $url ) : '' );
        }
    }

    /* ---------------------------
     * Frontend assets & overlay
     * --------------------------*/
    public function maybe_enqueue_frontend_assets() {
        if ( ! is_product() ) return;

        $product_id = get_queried_object_id();
        if ( ! $product_id ) return;

        $url = get_post_meta( $product_id, self::META_KEY, true );
        $video_id = $this->parse_youtube_id( $url );
        if ( ! $video_id ) return;

        // jQuery + Fancybox 3 (CDN)
        wp_enqueue_script( 'jquery' );
        wp_enqueue_style( 'fancybox-css', 'https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.css', [], '3.5.7' );
        wp_enqueue_script( 'fancybox-js',  'https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js', [ 'jquery' ], '3.5.7', true );

        // Minimal CSS to position the play icon
        $css = '
        .wc-yt-trigger{
            position:absolute;
            top:12px;
            left:12px;
            z-index:9;
            display:inline-flex;
            align-items:center;
            justify-content:center;
            width:64px;height:64px;
            border-radius:50%;
            background:#f51e46;
            opacity:.9;
            text-decoration:none;
        }
        .wc-yt-trigger svg{width:28px;height:28px;fill:#fff}
        .wc-yt-trigger:hover{opacity:1}
        .woocommerce-product-gallery{position:relative}
        ';
        wp_add_inline_style( 'fancybox-css', $css );
    }

    public function output_play_icon_overlay() {
        if ( ! is_product() ) return;

        global $product;
        if ( ! $product instanceof WC_Product ) return;

        $url = get_post_meta( $product->get_id(), self::META_KEY, true );
        $video_id = $this->parse_youtube_id( $url );
        if ( ! $video_id ) return;

        // Use a regular watch URL; Fancybox 3 understands YT links
        $watch = esc_url( "https://www.youtube.com/watch?v={$video_id}" );

        // Button overlay (accessible)
        echo '<a href="' . $watch . '" class="wc-yt-trigger" data-fancybox data-width="1280" data-height="720" aria-label="' . esc_attr__( 'Play product video', 'woocommerce' ) . '">
            <svg viewBox="0 0 448 512" aria-hidden="true" focusable="false"><path d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"/></svg>
        </a>';
    }

    /* ---------------------------
     * Helpers
     * --------------------------*/
    /**
     * Extract a YouTube video ID from various URL formats.
     * Supports: watch?v=, youtu.be/, shorts/
     *
     * @param string $url
     * @return string|null
     */
    private function parse_youtube_id( $url ) {
        if ( empty( $url ) || ! is_string( $url ) ) return null;

        // Normalize
        $url = trim( $url );

        // youtu.be/<id>
        if ( preg_match( '~(?:https?://)?(?:www\.)?youtu\.be/([a-zA-Z0-9_-]{6,})~', $url, $m ) ) {
            return $m[1];
        }

        // youtube.com/watch?v=<id> or &v=
        if ( preg_match( '~[?&]v=([a-zA-Z0-9_-]{6,})~', $url, $m ) ) {
            return $m[1];
        }

        // youtube.com/shorts/<id>
        if ( preg_match( '~(?:https?://)?(?:www\.)?youtube\.com/shorts/([a-zA-Z0-9_-]{6,})~', $url, $m ) ) {
            return $m[1];
        }

        return null;
    }
}

new WC_YouTube_URL_Field();


// ABFinder: /izzo-kereso/ + /izzo-kereso/* route-ok az Elementor oldalhoz
add_action( 'init', function () {

	// base oldal
	add_rewrite_rule(
		'^izzo-kereso/?$',
		'index.php?pagename=izzo-kereso&abf_page=1',
		'top'
	);

	// bármilyen alútvonal (bmw / bmw/x6 / bmw/x6/2019 / stb.)
	add_rewrite_rule(
		'^izzo-kereso/(.+)/?$',
		'index.php?pagename=izzo-kereso&abf_page=1&abf_path=$matches[1]',
		'top'
	);

}, 1 );

add_filter( 'query_vars', function ( $vars ) {
	$vars[] = 'abf_page';
	$vars[] = 'abf_path';

	return $vars;
} );

// WP canonical ne dobáljon át
add_filter( 'redirect_canonical', function ( $redirect_url ) {
	$req = $_SERVER['REQUEST_URI'] ?? '';
	if ( strpos( $req, '/izzo-kereso' ) === 0 ) {
		return false;
	}

	return $redirect_url;
}, 10, 1 );

// Rank Math canonical se “javítson”
add_filter( 'rank_math/frontend/canonical', function ( $canonical ) {
	$req = $_SERVER['REQUEST_URI'] ?? '';
	if ( strpos( $req, '/izzo-kereso' ) === 0 ) {
		return home_url( '/izzo-kereso/' );
	}

	return $canonical;
} );

add_action('admin_init', function () {
	if (!current_user_can('manage_options')) return;
	if (get_option('abf_flush_done')) return;
	flush_rewrite_rules();
	update_option('abf_flush_done', 1);
});

add_filter('request', function($q) {
	$uri = $_SERVER['REQUEST_URI'] ?? '';
	$path = trim(parse_url($uri, PHP_URL_PATH) ?? '', '/');

	// csak a pontos base route-ra
	if ($path === 'izzo-kereso') {
		// kényszerítjük oldalnak
		$q = [
			'pagename' => 'izzo-kereso',
			'abf_page' => 1,
		];
	}

	return $q;
});
add_filter('query_vars', function ($vars) {
	$vars[] = 'abf_page';
	$vars[] = 'abf_path';
	return $vars;
});

add_filter('vp_woo_pont_db_import_foxpost', function($points){
    $filtered = array();
    foreach ($points as $point) {
        if(strpos($point['name'], 'Z-BOX') !== 0) {
            $filtered[] = $point;
        }
    }
    return $filtered;
});