$settings ) { if ( ! empty( $settings['enabled'] ) ) { $any_enabled = true; break; } } if ( ! $any_enabled ) { return; } // Static layout config for the free version. $inline_js = 'document.body.dataset.aafMobileMode = "collapse"; document.body.dataset.aafDesktopMode = "always_visible";'; wp_add_inline_script( 'twincaaf_advanced_attribute_filters_fields_scripts_front', $inline_js ); echo ''; echo '
'; echo '
'; $filters_rendered = false; if ( ! empty( $filter_settings['category']['enabled'] ) ) { self::render_category_filter(); $filters_rendered = true; } foreach ( $filter_settings as $taxonomy => $settings ) { if ( 0 === strpos( $taxonomy, 'pa_' ) && ! empty( $settings['enabled'] ) ) { self::render_attribute_filter( $taxonomy ); $filters_rendered = true; } } echo '
'; if ( $filters_rendered ) { $reset_url = ''; if ( is_tax( 'product_cat' ) ) { $reset_url = get_term_link( get_queried_object_id(), 'product_cat' ); } elseif ( is_search() ) { $reset_url = home_url( '/' ) . '?s=' . get_search_query() . '&post_type=product'; } elseif ( is_shop() ) { $reset_url = wc_get_page_permalink( 'shop' ); } else { $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $reset_url = strtok( $request_uri, '?' ); } echo '
'; echo ''; echo '
'; } echo '
'; } } /** * Render attribute filter group. * * @param string $taxonomy Taxonomy slug. * @return void */ private static function render_attribute_filter( $taxonomy ) { $taxonomy_name = ( 0 === strpos( $taxonomy, 'pa_' ) ) ? $taxonomy : wc_attribute_taxonomy_name( str_replace( 'pa_', '', $taxonomy ) ); if ( 0 !== strpos( $taxonomy_name, 'pa_' ) ) { return; } $attribute_label = wc_attribute_label( $taxonomy_name ); $tax_query = array( 'relation' => 'AND' ); // 1. Context: Product Category. if ( is_tax( 'product_cat' ) ) { $tax_query[] = array( 'taxonomy' => 'product_cat', 'field' => 'term_id', 'terms' => get_queried_object_id(), 'include_children' => true, ); } // 2. Context: Brands (Supports WooCommerce Brands, Perfect Brands, PWB). $brand_taxonomies = array( 'product_brand', 'brand', 'pwb-brand' ); if ( is_tax( $brand_taxonomies ) ) { $current_brand = get_queried_object(); $tax_query[] = array( 'taxonomy' => $current_brand->taxonomy, 'field' => 'term_id', 'terms' => $current_brand->term_id, ); } // 3. Include existing URL filters to narrow down attributes (drill-down effect). $is_nonce_valid = ( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), 'twincaaf_filter_action' ) ); foreach ( $_GET as $key => $value ) { if ( ! $is_nonce_valid ) { break; } if ( strpos( $key, 'filter_' ) === 0 && ! empty( $value ) ) { $filter_tax = str_replace( 'filter_', 'pa_', $key ); if ( 'filter_brand' === $key ) { $filter_tax = taxonomy_exists( 'product_brand' ) ? 'product_brand' : ( taxonomy_exists( 'brand' ) ? 'brand' : 'pwb-brand' ); } if ( $filter_tax === $taxonomy_name ) { continue; } if ( taxonomy_exists( $filter_tax ) ) { $tax_query[] = array( 'taxonomy' => $filter_tax, 'field' => 'slug', 'terms' => explode( ',', sanitize_text_field( wp_unslash( $value ) ) ), 'operator' => 'IN', ); } } } static $query_cache = array(); $price_min = isset( $_GET['min_price'] ) ? floatval( $_GET['min_price'] ) : 0; $price_max = isset( $_GET['max_price'] ) ? floatval( $_GET['max_price'] ) : 0; $rating = isset( $_GET['rating_filter'] ) ? intval( $_GET['rating_filter'] ) : 0; $query_hash = md5( wp_json_encode( $tax_query ) . get_search_query() . $price_min . $price_max . $rating ); if ( ! isset( $query_cache[ $query_hash ] ) ) { $args = array( 'post_type' => 'product', 'posts_per_page' => -1, 'fields' => 'ids', 'post_status' => 'publish', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query 'tax_query' => $tax_query, 'no_found_rows' => true, ); if ( $price_min || $price_max ) { $args['meta_query'][] = array( 'key' => '_price', 'value' => array( $price_min, $price_max ), 'type' => 'numeric', 'compare' => 'BETWEEN', ); } if ( is_search() ) { $args['s'] = get_search_query(); } $query_cache[ $query_hash ] = get_posts( $args ); } $product_ids = $query_cache[ $query_hash ]; if ( empty( $product_ids ) ) { return; } $terms = wp_get_object_terms( $product_ids, $taxonomy_name, array( 'orderby' => 'name', 'order' => 'ASC', ) ); if ( is_wp_error( $terms ) || empty( $terms ) ) { return; } $unique_terms = array(); foreach ( $terms as $term ) { if ( isset( $unique_terms[ $term->slug ] ) ) { continue; } $term_product_ids = get_objects_in_term( $term->term_id, $taxonomy_name ); $matches = array_intersect( $product_ids, $term_product_ids ); $in_context_count = count( $matches ); if ( $in_context_count > 0 ) { $term->count = $in_context_count; $unique_terms[ $term->slug ] = $term; } } if ( empty( $unique_terms ) ) { return; } $type = 'checkbox'; echo '
'; echo '

' . esc_html( $attribute_label ) . '

'; echo '
'; $input_type = 'checkbox'; $input_name = $taxonomy_name . '[]'; echo ''; echo '
'; } /** * Build hierarchical category tree. * * @param array $terms List of category terms. * @param int $parent_id Parent category ID. * @return array */ private static function build_category_tree( $terms, $parent_id = 0 ) { $branch = array(); foreach ( $terms as $term ) { if ( (int) $term->parent === (int) $parent_id ) { $children = self::build_category_tree( $terms, $term->term_id ); $term->children = $children ? $children : array(); $branch[] = $term; } } return $branch; } /** * Recursive render for categories. * * @param array $tree Hierarchical category tree. * @param array $context Current filter context (checked IDs, ancestors). * @return void */ private static function render_category_tree_recursive( $tree, $context ) { foreach ( $tree as $term ) { $is_checked = in_array( $term->term_id, $context['checked'], true ) ? 'checked' : ''; $is_open = ( in_array( $term->term_id, $context['checked'], true ) || in_array( $term->term_id, $context['ancestors'], true ) ); $has_child = ! empty( $term->children ); $term_link = get_term_link( $term ); echo '
  • '; echo '
    '; if ( $has_child ) { echo ''; } echo '
    '; if ( $has_child ) { $display = $is_open ? 'display:block;' : 'display:none;'; echo ''; } echo '
  • '; } } /** * Render category filter group. * * @return void */ private static function render_category_filter() { $options = get_option( 'twincaaf_woocommerce_filters', array() ); if ( empty( $options['category']['enabled'] ) ) { return; } $terms = get_terms( array( 'taxonomy' => 'product_cat', 'hide_empty' => true, ) ); if ( is_wp_error( $terms ) || empty( $terms ) ) { return; } $tree = self::build_category_tree( $terms, 0 ); $current_obj = get_queried_object(); $current_ids = array(); if ( is_tax( 'product_cat' ) && $current_obj ) { $current_ids[] = $current_obj->term_id; } // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['product_cat'] ) ) { $cat_slugs = sanitize_text_field( wp_unslash( $_GET['product_cat'] ) ); $slugs = explode( ',', $cat_slugs ); foreach ( $slugs as $s ) { $t = get_term_by( 'slug', $s, 'product_cat' ); if ( $t ) { $current_ids[] = $t->term_id; } } } // phpcs:enable $ancestors = array(); foreach ( $current_ids as $cid ) { $pars = get_ancestors( $cid, 'product_cat' ); if ( $pars ) { $ancestors = array_merge( $ancestors, $pars ); } } $context = array( 'checked' => $current_ids, 'ancestors' => $ancestors, ); $is_group_open = ! empty( $current_ids ) ? 'open' : ''; echo '
    '; echo '

    ' . esc_html( mb_strtoupper( __( 'Categories', 'advanced-attribute-filters' ), 'UTF-8' ) ) . '

    '; echo '
    '; } }