4

As we know, to get terms of specific attribute added to a product we can use:

$attr_terms = $product->get_attribute( 'attr_slug' );

OR to get all terms of specific attribute regardless of a product we can use

$attr_terms = get_terms( 'pa_attr_slug' );

But how to get all attributes with their terms added to products of specific product category?

Something like:

$cat_attrs = ... ($cat->id);

foreach($cat_attrs as $cat_attr) {

    echo $cat_attr->name; // name of attribute

    foreach($cat_attr->terms as $term) {
        echo $term->name; // name of attribute term
    }
}
LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
stckvrw
  • 1,689
  • 18
  • 42

3 Answers3

6

To get an array of product attributes taxonomies/term names related to a product category, try the following:

// Here define the product category SLUG
$category_slug = 'posters';

$query_args = array(
    'status'    => 'publish',
    'limit'     => -1,
    'category'  => array( $category_slug ),
);

$data = array();
foreach( wc_get_products($query_args) as $product ){
    foreach( $product->get_attributes() as $taxonomy => $attribute ){
        $attribute_name = wc_attribute_label( $taxonomy ); // Attribute name
        // Or: $attribute_name = get_taxonomy( $taxonomy )->labels->singular_name;
        foreach ( $attribute->get_terms() as $term ){
            $data[$taxonomy][$term->term_id] = $term->name;
            // Or with the product attribute label name instead:
            // $data[$attribute_name][$term->term_id] = $term->name;
        }
    }
}

// Raw output (testing)
echo '<pre>'; print_r($data); echo '</pre>';

You will get something like (an example extract):

Array
(
    [pa_color] => Array
        (
            [9]  => Blue
            [10] => Green
        )
    [pa_size] => Array
        (
            [15] => Small
            [16] => Medium
            [18] => Large
        )
)
LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
  • Yes, I meant attribute_label, not attribute_name. Name, label... I've mixed them up. The names with the prefix `pa_` are like as "slugs" of attributes and the labels are "titles". But what's difference between `get_taxonomy( $taxonomy )->labels->singular_name` and `wc_attribute_label( $taxonomy )` ? – stckvrw Sep 30 '18 at 16:37
  • @stckvrw They both give the same result on Woocommerce product attribute taxonomies… Looking at [the source code of `wc_attribute_label()`](https://docs.woocommerce.com/wc-apidocs/source-function-wc_attribute_label.html#123-150) the function is heavier as it handle more features for other cases… – LoicTheAztec Sep 30 '18 at 16:56
  • Ok. One more question: the terms of each attribute are displayed not according to their admin sort ordering (I use custom ordering). While if I try to display terms of specific attribute like `$my_terms = get_terms('pa_some_attr_name');foreach($my_terms as $my_term){echo $my_term->name;}` the terms are displayed with correct ordering. How to resolve the issue? – stckvrw Sep 30 '18 at 18:18
  • @stckvrw This should be a new question as it can't be answered in a comment. – LoicTheAztec Sep 30 '18 at 20:20
1

We've used the accepted approach first but in the case when the product category had hundreds of products it reached the memory limit. To optimize this we transformed it into a raw query.

Hopefully this comes in handy :)

    global $wpdb;

$attributes_query = $wpdb->prepare(
    "SELECT tr.object_id, tt.taxonomy, tt.term_id, t.name
    FROM {$wpdb->prefix}term_relationships AS tr
    JOIN {$wpdb->prefix}term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
    JOIN {$wpdb->prefix}terms AS t ON tt.term_id = t.term_id
    WHERE tr.object_id IN (
        SELECT p.ID
        FROM {$wpdb->prefix}posts AS p
        JOIN {$wpdb->prefix}term_relationships AS tr ON p.ID = tr.object_id
        JOIN {$wpdb->prefix}term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
        JOIN {$wpdb->prefix}terms AS t ON tt.term_id = t.term_id
        WHERE p.post_type = 'product'
        AND p.post_status = 'publish'
        AND tt.taxonomy = 'product_cat'
        AND t.slug = '{$args['category']}'
    )
    AND tt.taxonomy LIKE %s",
    'pa_%'
);

$results = $wpdb->get_results($attributes_query);
Nessario
  • 11
  • 2
0

I improved the great code from Nessario a little bit to add more information to the output.

global $wpdb;
$category_slug=get_queried_object()->slug??'';
$attributes_query = $wpdb->prepare(
    "SELECT t.name as taxonomy_name,tr.object_id, tt.taxonomy, tt.term_id, t.name, count(*) as count
    FROM {$wpdb->prefix}term_relationships AS tr
    JOIN {$wpdb->prefix}term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
    JOIN {$wpdb->prefix}terms AS t ON tt.term_id = t.term_id
    WHERE tr.object_id IN (
        SELECT p.ID
        FROM {$wpdb->prefix}posts AS p
        JOIN {$wpdb->prefix}term_relationships AS tr ON p.ID = tr.object_id
        JOIN {$wpdb->prefix}term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
        JOIN {$wpdb->prefix}terms AS t ON tt.term_id = t.term_id
        WHERE p.post_type = 'product'
        AND p.post_status = 'publish'
        AND tt.taxonomy = 'product_cat'
        AND ('$category_slug'='' OR t.slug = '$category_slug')
    )
    AND tt.taxonomy LIKE %s
    GROUP By tt.term_id",
    'pa_%'
);
$terms=$wpdb->get_results($attributes_query);
$filters=array();
foreach($terms as $term){
    if(!isset($filters[$term->taxonomy])){
        $filters[$term->taxonomy]=array(
            'name'=>wc_attribute_label($term->taxonomy),
            'count'=>0,
            'options'=>array()
        );
    }
    $filters[$term->taxonomy]['count']+=$term->count;
    $filters[$term->taxonomy]['options'][]=array(
        'term_id'=>$term->term_id,
        'name'=>$term->name,
        'count'=>$term->count,
    );
}

In the end you get an array with all used attributes, their used terms, and the product counts. This is ideal if you want to implement individual filters based on the current group.

B. Martin
  • 1,047
  • 12
  • 18