/*
Theme Name: Astra Child
Theme URI: https://wpastra.com/
Template: astra
Author: Brainstorm Force
Author URI: https://wpastra.com/about/?utm_source=theme_preview&utm_medium=author_link&utm_campaign=astra_theme
Description: The Astra WordPress theme is lightning-fast and highly customizable. It has over 1 million downloads and the only theme in the world with 5,700+ five-star reviews! It’s ideal for professional web designers, solopreneurs, small businesses, eCommerce, membership sites and any type of website. It offers special features and templates so it works perfectly with all page builders like Spectra, Elementor, Beaver Builder, etc. Fast performance, clean code, mobile-first design and schema markup are all built-in, making the theme exceptionally SEO-friendly. It’s fully compatible with WooCommerce, SureCart and other eCommerce plugins and comes with lots of store-friendly features and templates. Astra also provides expert support for free users. A dedicated team of fully trained WordPress experts are on hand to help with every aspect of the theme. Try the live demo of Astra: https://zipwp.org/themes/astra/
Tags: custom-menu,custom-logo,entertainment,one-column,two-columns,left-sidebar,e-commerce,right-sidebar,custom-colors,editor-style,featured-images,full-width-template,microformats,post-formats,rtl-language-support,theme-options,threaded-comments,translation-ready,blog
Version: 4.11.12.1758835603
Updated: 2025-09-25 21:26:43

*/


/* -----------------------------------------------
 * CUSTOM AJAX PRODUCT FILTERS (Elementor-ready)
 * Shortcode: [custom_product_filters]
 * Requirements: WooCommerce active
 * ---------------------------------------------*/

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

/**
 * Enqueue assets for the filter block
 */
add_action('wp_enqueue_scripts', function () {
    // Only enqueue on pages that actually use the shortcode
    if (!is_singular() && !is_shop() && !is_product_category() && !is_page()) return;

    // A tiny CSS to match a clean, grid layout (safe to tweak)
    $css = "
    .cpf-wrap{display:grid;grid-template-columns:280px 1fr;gap:24px}
    .cpf-sidebar{background:#f6f7f9;border:1px solid #e7e8ec;border-radius:8px;padding:16px;position:sticky;top:24px;height:max-content}
    .cpf-section{margin-bottom:18px}
    .cpf-section h4{margin:0 0 10px;font-size:14px;letter-spacing:.02em;text-transform:uppercase;color:#333}
    .cpf-checks label{display:flex;gap:8px;align-items:center;margin:6px 0;font-size:14px}
    .cpf-price-row{display:flex;gap:10px;align-items:center}
    .cpf-price-row input{width:100%}
    .cpf-quick{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
    .cpf-quick button{border:1px solid #dcdfe4;background:#fff;padding:6px 10px;border-radius:6px;cursor:pointer}
    .cpf-actions{display:flex;gap:8px;margin-top:8px}
    .cpf-actions button{border:0;background:#111;color:#fff;padding:10px 12px;border-radius:6px;cursor:pointer}
    .cpf-search{display:flex;gap:8px}
    .cpf-search input{flex:1;border:1px solid #dcdfe4;border-radius:6px;padding:8px 10px}
    .cpf-products{min-height:200px}
    .cpf-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}
    .cpf-card{border:1px solid #eceff3;border-radius:10px;overflow:hidden;background:#fff;display:flex;flex-direction:column}
    .cpf-thumb{aspect-ratio:1/1;display:flex;align-items:center;justify-content:center;background:#fafafa}
    .cpf-thumb img{max-width:100%;max-height:100%;object-fit:contain}
    .cpf-body{padding:12px}
    .cpf-title{font-size:15px;margin:0 0 6px}
    .cpf-price{font-weight:600;margin:4px 0 8px}
    .cpf-meta{font-size:12px;color:#777;margin-bottom:10px}
    .cpf-add{margin-top:auto;padding:12px}
    .cpf-empty{padding:24px;text-align:center;color:#666}
    .cpf-pagi{display:flex;gap:8px;justify-content:center;margin-top:18px}
    .cpf-pagi button{border:1px solid #dcdfe4;background:#fff;padding:8px 12px;border-radius:6px;cursor:pointer}
    .cpf-pagi button[disabled]{opacity:.5;cursor:not-allowed}
    @media (max-width: 1024px){.cpf-grid{grid-template-columns:repeat(2,1fr)}}
    @media (max-width: 720px){.cpf-wrap{grid-template-columns:1fr}.cpf-grid{grid-template-columns:1fr}}
    ";
    wp_register_style('cpf-style', false);
    wp_enqueue_style('cpf-style');
    wp_add_inline_style('cpf-style', $css);

    // JS (vanilla) for AJAX filtering
    wp_enqueue_script('cpf-script', '', [], false, true);
    wp_add_inline_script('cpf-script', "
      (function(){
        const el = document;
        function qs(s,scope){return (scope||el).querySelector(s)}
        function qsa(s,scope){return Array.from((scope||el).querySelectorAll(s))}
        function serialize(form){
          const fd=new FormData(form), obj={};
          for(const [k,v] of fd.entries()){
            if(obj[k]){ if(!Array.isArray(obj[k])) obj[k]=[obj[k]]; obj[k].push(v); }
            else obj[k]=v;
          }
          return obj;
        }
        function fetchProducts(root, page){
          const form = qs('.cpf-form',root);
          const args = serialize(form);
          if(page) args.page = page;
          args.action = 'cpf_filter';
          args._ajax_nonce = root.dataset.nonce;

          const results = qs('.cpf-products',root);
          results.setAttribute('aria-busy','true');
          results.style.opacity = '.6';

          fetch('".admin_url('admin-ajax.php')."',{
            method:'POST',
            headers:{'Accept':'text/html'},
            body:new URLSearchParams(args)
          })
          .then(r=>r.text())
          .then(html=>{
            results.innerHTML = html;
          })
          .catch(()=>{ results.innerHTML = '<div class=\"cpf-empty\">Something went wrong. Please try again.</div>'; })
          .finally(()=>{
            results.removeAttribute('aria-busy');
            results.style.opacity = '1';
            attachPagination(root);
          });
        }
        function attachPagination(root){
          qsa('.cpf-pagi button[data-page]',root).forEach(btn=>{
            btn.addEventListener('click', e=>{
              e.preventDefault();
              fetchProducts(root, btn.dataset.page);
            });
          });
        }
        function bind(root){
          const form = qs('.cpf-form',root);

          // Trigger on any change / submit
          form.addEventListener('change', ()=>fetchProducts(root, 1));
          form.addEventListener('submit', e=>{
            e.preventDefault();
            fetchProducts(root, 1);
          });

          // Quick ranges
          qsa('.cpf-quick button',root).forEach(b=>{
            b.addEventListener('click', e=>{
              e.preventDefault();
              const min = b.dataset.min || '';
              const max = b.dataset.max || '';
              qs('[name=min_price]',root).value = min;
              qs('[name=max_price]',root).value = max;
              fetchProducts(root, 1);
            });
          });

          // Reset
          qs('.cpf-reset',root).addEventListener('click', e=>{
            e.preventDefault();
            form.reset();
            // clear all checked categories
            qsa('.cpf-cats input[type=checkbox]',root).forEach(i=>i.checked=false);
            fetchProducts(root, 1);
          });

          attachPagination(root);
        }

        // Boot
        document.addEventListener('DOMContentLoaded', function(){
          qsa('.cpf-root').forEach(bind);
        });
      })();
    ");
});

/**
 * Shortcode renderer
 */
add_shortcode('custom_product_filters', function ($atts) {
    if (!class_exists('WooCommerce')) {
        return '<p><em>WooCommerce is required for product filters.</em></p>';
    }

    $atts = shortcode_atts([
        'per_page' => 9,        // products per page
        'columns'  => 3,        // grid columns (CSS already handles responsiveness)
    ], $atts, 'custom_product_filters');

    // Build category list (top level + children)
    $cats = get_terms([
        'taxonomy'   => 'product_cat',
        'hide_empty' => true,
        'parent'     => 0
    ]);

    ob_start();
    ?>
    <div class="cpf-root" data-nonce="<?php echo esc_attr(wp_create_nonce('cpf_nonce')); ?>">
      <div class="cpf-wrap">
        <aside class="cpf-sidebar">
          <form class="cpf-form" method="post">
            <input type="hidden" name="per_page" value="<?php echo (int)$atts['per_page']; ?>">
            <div class="cpf-section">
              <h4>Search By Product</h4>
              <div class="cpf-search">
                <input type="text" name="keyword" placeholder="Search products...">
                <button class="cpf-apply" type="submit">Search</button>
              </div>
            </div>

            <div class="cpf-section">
              <h4>Filters</h4>
              <div class="cpf-checks">
                <label><input type="checkbox" name="on_sale" value="1"> On Sale</label>
                <label><input type="checkbox" name="best_seller" value="1"> Best Seller</label>
                <label><input type="checkbox" name="in_stock" value="1"> In Stock Now</label>
              </div>
            </div>

            <div class="cpf-section">
              <h4>Price</h4>
              <div class="cpf-price-row">
                <input type="number" step="1" min="0" name="min_price" placeholder="Min">
                <span>—</span>
                <input type="number" step="1" min="0" name="max_price" placeholder="Max">
              </div>
              <div class="cpf-quick">
                <button data-min="0" data-max="100">$0–$100</button>
                <button data-min="100" data-max="500">$100–$500</button>
                <button data-min="500" data-max="1000">$500–$1000</button>
                <button data-min="1000" data-max="">$1000+</button>
              </div>
            </div>

            <div class="cpf-section cpf-cats">
              <h4>Category</h4>
              <?php
              if ($cats && !is_wp_error($cats)) :
                foreach ($cats as $top) :
                  ?>
                  <label><input type="checkbox" name="categories[]" value="<?php echo (int)$top->term_id; ?>"> <?php echo esc_html($top->name); ?></label>
                  <?php
                  // children
                  $kids = get_terms([
                      'taxonomy' => 'product_cat',
                      'hide_empty' => true,
                      'parent' => $top->term_id
                  ]);
                  if ($kids && !is_wp_error($kids)) :
                    foreach ($kids as $child) :
                      ?>
                      <label style="margin-left:16px;"><input type="checkbox" name="categories[]" value="<?php echo (int)$child->term_id; ?>"> <?php echo esc_html($child->name); ?></label>
                      <?php
                    endforeach;
                  endif;
                endforeach;
              endif;
              ?>
            </div>

            <div class="cpf-section cpf-actions">
              <button type="submit" class="cpf-apply">Apply</button>
              <button class="cpf-reset">Reset</button>
            </div>
          </form>
        </aside>

        <section class="cpf-results">
          <div class="cpf-products">
            <?php echo cpf_render_products_html([
                'page'     => 1,
                'per_page' => (int)$atts['per_page'],
            ]); ?>
          </div>
        </section>
      </div>
    </div>
    <?php
    return ob_get_clean();
});

/**
 * AJAX handler
 */
add_action('wp_ajax_cpf_filter', 'cpf_ajax_filter');
add_action('wp_ajax_nopriv_cpf_filter', 'cpf_ajax_filter');

function cpf_ajax_filter() {
    check_ajax_referer('cpf_nonce');

    $page      = isset($_POST['page']) ? max(1, (int)$_POST['page']) : 1;
    $per_page  = isset($_POST['per_page']) ? max(1, (int)$_POST['per_page']) : 9;

    $args = [
        'page'     => $page,
        'per_page' => $per_page,
        'keyword'  => sanitize_text_field($_POST['keyword'] ?? ''),
        'on_sale'  => !empty($_POST['on_sale']),
        'best'     => !empty($_POST['best_seller']),
        'instock'  => !empty($_POST['in_stock']),
        'min'      => ($_POST['min_price'] ?? '') !== '' ? floatval($_POST['min_price']) : '',
        'max'      => ($_POST['max_price'] ?? '') !== '' ? floatval($_POST['max_price']) : '',
        'cats'     => array_map('intval', (array)($_POST['categories'] ?? [])),
    ];

    echo cpf_render_products_html($args);
    wp_die();
}

/**
 * Core query + renderer (shared by shortcode initial render and AJAX)
 */
function cpf_render_products_html($opts = []) {
    $page     = max(1, (int)($opts['page'] ?? 1));
    $per_page = max(1, (int)($opts['per_page'] ?? 9));
    $keyword  = sanitize_text_field($opts['keyword'] ?? '');
    $on_sale  = !empty($opts['on_sale']);
    $best     = !empty($opts['best']);
    $instock  = !empty($opts['instock']);
    $min      = $opts['min'] === '' ? '' : floatval($opts['min']);
    $max      = $opts['max'] === '' ? '' : floatval($opts['max']);
    $cats     = array_filter(array_map('intval', (array)($opts['cats'] ?? [])));

    $meta_query = [];
    $tax_query  = [];

    // Price range
    if ($min !== '' || $max !== '') {
        $range = ['key' => '_price', 'type' => 'DECIMAL', 'compare' => 'BETWEEN', 'value' => [0, 999999]];
        if ($min !== '') $range['value'][0] = $min;
        if ($max !== '') $range['value'][1] = $max;
        $meta_query[] = $range;
    }

    // In stock
    if ($instock) {
        $meta_query[] = [
            'key'     => '_stock_status',
            'value'   => 'instock',
            'compare' => '='
        ];
    }

    // Categories
    if (!empty($cats)) {
        $tax_query[] = [
            'taxonomy' => 'product_cat',
            'field'    => 'term_id',
            'terms'    => $cats,
            'operator' => 'IN',
        ];
    }

    // Base args
    $query_args = [
        'post_type'      => 'product',
        'post_status'    => 'publish',
        'paged'          => $page,
        'posts_per_page' => $per_page,
        's'              => $keyword,
        'meta_query'     => $meta_query,
        'tax_query'      => $tax_query,
    ];

    // On sale
    if ($on_sale) {
        $sale_ids = wc_get_product_ids_on_sale();
        $sale_ids = empty($sale_ids) ? [0] : $sale_ids;
        $query_args['post__in'] = $sale_ids;
    }

    // Best Seller (order by total_sales)
    if ($best) {
        $query_args['meta_key'] = 'total_sales';
        $query_args['orderby']  = 'meta_value_num';
        $query_args['order']    = 'DESC';
    } else {
        // default: newest
        $query_args['orderby']  = 'date';
        $query_args['order']    = 'DESC';
    }

    $q = new WP_Query($query_args);

    ob_start();

    if ($q->have_posts()) {
        echo '<div class="cpf-grid">';
        while ($q->have_posts()) {
            $q->the_post();
            $pid = get_the_ID();
            $product = wc_get_product($pid);
            if (!$product) continue;

            $price_html = $product->get_price_html();
            $rating_cnt = $product->get_rating_count();
            $avg_rating = $product->get_average_rating();
            ?>
            <article class="cpf-card">
              <a class="cpf-thumb" href="<?php the_permalink(); ?>">
                <?php echo wp_get_attachment_image($product->get_image_id(), 'woocommerce_thumbnail'); ?>
              </a>
              <div class="cpf-body">
                <h3 class="cpf-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
                <div class="cpf-price"><?php echo wp_kses_post($price_html); ?></div>
                <?php if ($rating_cnt): ?>
                  <div class="cpf-meta"><?php echo esc_html(number_format($avg_rating,1)); ?> ★ (<?php echo (int)$rating_cnt; ?>)</div>
                <?php endif; ?>
              </div>
              <div class="cpf-add">
                <?php woocommerce_template_loop_add_to_cart(); ?>
              </div>
            </article>
            <?php
        }
        echo '</div>';

        // Pagination
        $total = (int)$q->max_num_pages;
        if ($total > 1) {
            echo '<div class="cpf-pagi">';
            $prev_disabled = $page <= 1 ? 'disabled' : '';
            echo '<button '.$prev_disabled.' data-page="'.max(1,$page-1).'">« Prev</button>';
            // simple window
            $start = max(1, $page-2);
            $end   = min($total, $page+2);
            for ($i=$start;$i<=$end;$i++){
                $is = $i==$page ? 'disabled' : '';
                echo '<button '.$is.' data-page="'.$i.'">'.$i.'</button>';
            }
            $next_disabled = $page >= $total ? 'disabled' : '';
            echo '<button '.$next_disabled.' data-page="'.min($total,$page+1).'">Next »</button>';
            echo '</div>';
        }

        wp_reset_postdata();
    } else {
        echo '<div class="cpf-empty">No products match your filters.</div>';
    }

    return ob_get_clean();
}


