=== zen_get_brand_checkbox_html ===
function zen_get_brand_checkbox_html( $cat_slugs, $selected_brands = array() ) {
$brands = zen_get_cascaded_brands( $cat_slugs );
$html = '';
if ( empty( $brands ) || is_wp_error( $brands ) ) {
return $html;
}
foreach ( $brands as $brand ) {
$checked_parent = in_array( $brand->slug, $selected_brands ) ? 'checked' : '';
$html .= '';
$sub_brands = isset( $brand->zen_children ) ? $brand->zen_children : array();
if ( !empty( $sub_brands ) && !is_wp_error( $sub_brands ) ) {
$html .= '
';
foreach ( $sub_brands as $sub ) {
$checked_child = in_array( $sub->slug, $selected_brands ) ? 'checked' : '';
$html .= '';
}
$html .= '
';
}
}
return $html;
}
/**
* SHOP PAGE — Map of brand slug => associated Solution (category) slugs.
* Powers the sidebar behaviour where checking a Brand first auto-selects
* the Solution(s) that brand's products actually belong to.
*/
function zen_get_brand_category_map() {
$map = array();
$product_ids = get_posts( array( 'post_type' => 'product', 'posts_per_page' => -1, 'fields' => 'ids' ) );
if ( empty( $product_ids ) ) {
return $map;
}
$brand_pairs = wp_get_object_terms( $product_ids, 'product_brand', array( 'fields' => 'all_with_object_id' ) );
$cat_pairs = wp_get_object_terms( $product_ids, 'product_cat', array( 'fields' => 'all_with_object_id' ) );
if ( is_wp_error( $brand_pairs ) || is_wp_error( $cat_pairs ) ) {
return $map;
}
$cats_by_product = array();
foreach ( $cat_pairs as $cp ) {
if ( is_wp_error( $cp ) || $cp->slug === 'uncategorized' ) { continue; }
if ( !isset( $cats_by_product[ $cp->object_id ] ) ) { $cats_by_product[ $cp->object_id ] = array(); }
$cats_by_product[ $cp->object_id ][] = $cp->slug;
}
foreach ( $brand_pairs as $bp ) {
if ( is_wp_error( $bp ) ) { continue; }
$cat_slugs = isset( $cats_by_product[ $bp->object_id ] ) ? $cats_by_product[ $bp->object_id ] : array();
if ( empty( $cat_slugs ) ) { continue;
=== zen_ajax_filter_products ===
function zen_ajax_filter_products() {
$tax_query = array();
if ( !empty($_POST['product_cat']) ) {
$tax_query[] = array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => explode(',', sanitize_text_field(wp_unslash($_POST['product_cat']))),
'operator' => 'IN',
);
}
if ( !empty($_POST['product_brand']) ) {
$tax_query[] = array(
'taxonomy' => 'product_brand',
'field' => 'slug',
'terms' => explode(',', sanitize_text_field(wp_unslash($_POST['product_brand']))),
'operator' => 'IN',
);
}
if ( !empty($_POST['product_technology']) ) {
$tax_query[] = array(
'taxonomy' => 'pa_technology',
'field' => 'slug',
'terms' => explode(',', sanitize_text_field(wp_unslash($_POST['product_technology']))),
'operator' => 'IN',
);
}
if ( !empty($_POST['product_connectivity']) ) {
$tax_query[] = array(
'taxonomy' => 'pa_connectivity',
'field' => 'slug',
'terms' => explode(',', sanitize_text_field(wp_unslash($_POST['product_connectivity']))),
'operator' => 'IN',
);
}
$paged = !empty($_POST['paged']) ? absint($_POST['paged']) : 1;
$sort_by = !empty($_POST['sort_by']) ? sanitize_text_field(wp_unslash($_POST['sort_by'])) : '';
$result = zen_shop_render_products( $tax_query, $paged, $sort_by );
$posted_cats = !empty($_POST['product_cat']) ? explode(',', sanitize_text_field(wp_unslash($_POST['product_cat']))) : array();
$cat_single = ( count($posted_cats) === 1 ) ? trim($posted_cats[0]) : '';
$posted_brands = !empty($_POST['product_brand']) ? explode(',', sanitize_text_field(wp_unslash($_POST['product_brand']))) : array();
$brand_single = ( count($posted_brands) === 1 ) ? trim($posted_brands[0]) : '';
$result['brand_options_html'] = zen_get_brand_options_html( $posted_cats, $brand_single );
$result['brand_checkboxes_html'] = zen_get_brand_checkbox_html( $posted_cats, $posted_brands );
$result['hero_title_line1'] = zen_get_hero_title_line1( $posted_cats, $posted_brands );
$result['active_filter_summary'] = zen_get_active_filter_summary( $posted_cats, $posted_brands );
wp_send_json_success( $result );
}
/* ============================================================
SHOP PAGE — Brand dropdown options (cascading with Solutions)
Shared by the initial page render and the AJAX filter handler
so both stay in sync automatically.
============================================================ */
function zen_get_cascaded_brands( $cat_slugs ) {
$cat_slugs = array_values( array_filter( array_map( 'trim', (array) $cat_slugs ) ) );
if ( !empty( $cat_slugs ) ) {
$product_ids = get_posts( array(
'post_type' => 'product',
'posts_per_page' => -1,
'fields' => 'ids',
'tax_query' => array( array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => $cat_slugs,
'operator' => 'IN',
) ),
) );
$brands_raw = !empty( $product_ids )
? wp_get_object_terms( $product_ids, 'product_brand', array( 'fields' => 'all_with_object_id' ) )
: array();
// Tally how many of the
=== zen_shop_render_products ===
function zen_shop_render_products( $tax_query, $paged = 1, $sort_by = '' ) {
$args = array(
'post_type' => 'product',
'posts_per_page' => 36,
'paged' => $paged,
'tax_query' => $tax_query,
);
if ( $sort_by === 'date' ) {
$args['orderby'] = 'date';
$args['order'] = 'DESC';
} elseif ( $sort_by === 'title' ) {
$args['orderby'] = 'title';
$args['order'] = 'ASC';
} elseif ( $sort_by === 'popularity' ) {
$args['meta_key'] = 'total_sales';
$args['orderby'] = 'meta_value_num';
}
$query = new WP_Query( $args );
ob_start();
$zen_cat_brand_cache = array();
?>
have_posts()): ?>
have_posts()): $query->the_post(); global $product; ?>
get_date_created());
$is_new = ( time() - $created ) < ( $newness_days * DAY_IN_SECONDS );
$total_sales = $product->get_total_sales();
$is_popular = $total_sales > 50; // adjust threshold
?>
slug !== 'uncategorized'; } ) ) : array();
$card_brand_terms = get_the_terms( get_the_ID(), 'product_brand' );
$card_brand_terms = ( $card_brand_terms && !is_wp_error( $card_brand_terms ) ) ? $card_brand_terms : array();
$card_brand_top = null;
if ( count( $card_brand_terms ) >= 1 ) {
$first_brand_term = $card_brand_terms[0];
$card_brand_top = $first_brand_term->parent ? get_term( $first_brand_term->parent, 'product_brand' ) : $first_brand_term;
}
$card_solution_label = 'Generic';
$card_brand_label = ( $card_brand_top && !is_wp_error( $card_brand_top ) ) ? $card_brand_top->name : '';
if ( count( $card_cat_terms ) === 1 ) {
$cat_slug = $card_cat_terms[0]->slug;
$brand_matches_cat = true;
if ( $card_brand_top && !is_wp_error( $card_brand_top ) ) {
if ( !isset( $zen_cat_brand_cache[ $cat_slug ] ) ) {
$cat_brand_terms = zen_get_cascaded_brands( $cat_slug );
$zen_cat_brand_cache[ $cat_slug ] = is_array( $cat_brand_terms ) ? wp_list_pluck( $cat_brand_terms, 'term_id' ) : array();
}
$brand_matches_cat = in_array( $card_brand_top->term_id, $zen_cat_brand_cache[ $cat_slug ] );
}
if ( $brand_matches_cat ) {
$card_solution_label = $card_cat_terms[0]->name;
}
}
?>