76

I am trying to get the product categories from WooCommerce through a function in my WordPress theme

    function get_me_list_of($atts, $content = null)
    {   
        $args = array( 'post_type' => 'product', 'posts_per_page' => 10, 'product_cat' => $atts[0]);

        $loop = new WP_Query( $args );

        echo '<h1 class="upp">Style '.$atts[0].'</h1>';
        echo "<ul class='mylisting'>";
        while ( $loop->have_posts() ) : $loop->the_post(); 
        global $product; 

        echo '<li><a href="'.get_permalink().'">'.get_the_post_thumbnail($loop->post->ID, 'thumbnail').'</a></li>';
     echo '<li><a href="'.get_permalink().'">'.$loop->post->post_title.'</a></li>';

echo '<li><a href="">'.get_categories().'</a></li>';
        endwhile; 

        echo "</ul>";

        wp_reset_query(); 


    }

    ?>

The above code returns some products, but the product categories.

When I included echo '<li><a href="">'.get_categories().'</a></li>'; in the code above it returns as an array. How do I fix this?

How do i change this to get the product categories from WooCommerce?

Michael
  • 589
  • 2
  • 11
  • 26
Tester
  • 2,887
  • 10
  • 30
  • 60

9 Answers9

191
<?php

  $taxonomy     = 'product_cat';
  $orderby      = 'name';  
  $show_count   = 0;      // 1 for yes, 0 for no
  $pad_counts   = 0;      // 1 for yes, 0 for no
  $hierarchical = 1;      // 1 for yes, 0 for no  
  $title        = '';  
  $empty        = 0;

  $args = array(
         'taxonomy'     => $taxonomy,
         'orderby'      => $orderby,
         'show_count'   => $show_count,
         'pad_counts'   => $pad_counts,
         'hierarchical' => $hierarchical,
         'title_li'     => $title,
         'hide_empty'   => $empty
  );
 $all_categories = get_categories( $args );
 foreach ($all_categories as $cat) {
    if($cat->category_parent == 0) {
        $category_id = $cat->term_id;       
        echo '<br /><a href="'. get_term_link($cat->slug, 'product_cat') .'">'. $cat->name .'</a>';

        $args2 = array(
                'taxonomy'     => $taxonomy,
                'child_of'     => 0,
                'parent'       => $category_id,
                'orderby'      => $orderby,
                'show_count'   => $show_count,
                'pad_counts'   => $pad_counts,
                'hierarchical' => $hierarchical,
                'title_li'     => $title,
                'hide_empty'   => $empty
        );
        $sub_cats = get_categories( $args2 );
        if($sub_cats) {
            foreach($sub_cats as $sub_category) {
                echo  $sub_category->name ;
            }   
        }
    }       
}
?>

This will list all the top level categories and subcategories under them hierarchically. do not use the inner query if you just want to display the top level categories. Style it as you like.

subZero
  • 5,056
  • 6
  • 31
  • 51
Suman.hassan95
  • 3,895
  • 8
  • 26
  • 24
24

Improving Suman.hassan95's answer by adding a link to subcategory as well. Replace the following code:

$sub_cats = get_categories( $args2 );
    if($sub_cats) {
        foreach($sub_cats as $sub_category) {
            echo  $sub_category->name ;
        }

    }

with:

$sub_cats = get_categories( $args2 );
            if($sub_cats) {
                foreach($sub_cats as $sub_category) {
                    echo  '<br/><a href="'. get_term_link($sub_category->slug, 'product_cat') .'">'. $sub_category->name .'</a>';
                }
            }

or if you also wish a counter for each subcategory, replace with this:

$sub_cats = get_categories( $args2 );
            if($sub_cats) {
                foreach($sub_cats as $sub_category) {
                    echo  '<br/><a href="'. get_term_link($sub_category->slug, 'product_cat') .'">'. $sub_category->name .'</a>';
                    echo apply_filters( 'woocommerce_subcategory_count_html', ' <span class="cat-count">' . $sub_category->count . '</span>', $category );
                }
            }
Xris Pap
  • 241
  • 2
  • 3
11

You could also use wp_list_categories();

wp_list_categories( array('taxonomy' => 'product_cat', 'title_li'  => '') );
Etienne Dupuis
  • 13,548
  • 6
  • 47
  • 58
  • 2
    This doesn't show child categories which is often going to be important – AdamJones Dec 10 '19 at 19:53
  • 2
    This is very useful! Using wp_list_categories for product cats is slick. It gives all the WordPress built-in goodies like the .cat-item classes and the .current-cat class for the active category. Good looking out on the share @Etienne Dupuis – Taine Feb 20 '21 at 07:05
8

Efficient solution for getting all categories and sub-categories, no matter the depth.

    /**
     * Lists all product categories and sub-categories in a tree structure.
     *
     * @return array
     */
    function list_product_categories() {
        $categories = get_terms(
            array(
                'taxonomy'   => 'product_cat',
                'orderby'    => 'name',
                'hide_empty' => false,
            )
        );

        $categories = treeify_terms($categories);

        return $categories;
    }

    /**
     * Converts a flat array of terms into a hierarchical tree structure.
     *
     * @param WP_Term[] $terms Terms to sort.
     * @param integer   $root_id Id of the term which is considered the root of the tree.
     *
     * @return array Returns an array of term data. Note the term data is an array, rather than
     * term object.
     */
    function treeify_terms($terms, $root_id = 0) {
        $tree = array();

        foreach ($terms as $term) {
            if ($term->parent === $root_id) {
                array_push(
                    $tree,
                    array(
                        'name'     => $term->name,
                        'slug'     => $term->slug,
                        'id'       => $term->term_id,
                        'count'    => $term->count,
                        'children' => treeify_terms($terms, $term->term_id),
                    )
                );
            }
        }

        return $tree;
    }

It's also much more efficient than the current top answer as it uses only one query.

Maciej Krawczyk
  • 14,825
  • 5
  • 55
  • 67
  • Thank you - you saved me so much time today! – jmotes Aug 08 '22 at 01:00
  • This part: 'children' => treeify_terms($terms, $term->term_id), coule be modified not to add 'children' node if empty - but apart from that - excellent solution. Thank you. – Andrzej Dzirba Mar 22 '23 at 06:04
  • @Andrzej Why would that be better? You'd get extra processing overhead to check if the property is defined rather than just iterating an array - whether it's empty or not. – Maciej Krawczyk Jul 05 '23 at 13:31
  • @MaciejKrawczyk It depends what you're trying to do with that data. If you'd want to send it over rest api, for example - the less you sent the better. That's why I prefere not to have empty properties. – Andrzej Dzirba Jul 06 '23 at 14:45
  • @AndrzejDzirba That's a good point. – Maciej Krawczyk Jul 06 '23 at 16:15
3

In my opinion this is the simplest solution

$orderby = 'name';
                $order = 'asc';
                $hide_empty = false ;
                $cat_args = array(
                    'orderby'    => $orderby,
                    'order'      => $order,
                    'hide_empty' => $hide_empty,
                );

                $product_categories = get_terms( 'product_cat', $cat_args );

                if( !empty($product_categories) ){
                    echo '

                <ul>';
                    foreach ($product_categories as $key => $category) {
                        echo '

                <li>';
                        echo '<a href="'.get_term_link($category).'" >';
                        echo $category->name;
                        echo '</a>';
                        echo '</li>';
                    }
                    echo '</ul>


                ';
                }
  • While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – Piotr Labunski Feb 20 '20 at 10:37
  • the ul schema looks nice and handy. Can I have the subcategories nested under every parent category? – Bill Jan 24 '21 at 09:33
0
//Recursive function

function wc_loop_categories($parent = 0)
{

    global $wpdb;

    $query = "SELECT t.term_id AS ID, t.name AS title  
        FROM {$wpdb->prefix}terms AS t
            LEFT JOIN {$wpdb->prefix}term_taxonomy AS ta
                ON ta.term_id = t.term_id            
            WHERE ta.taxonomy='product_cat'
                AND ta.parent=$parent
            ORDER BY t.name ASC";

    $cats = $wpdb->get_results($query);

    foreach ($cats as $key => $cat) {
        // get all sub_cats from current loop item
        $cats[$key]->sub_cats = wc_loop_categories($cat->ID);
    }

    return $cats;
}
0

I have prepared the function that recursively creating menu and adding to object child element! masterCategoryId is the category rootId that recursion should work. FYI I did changes on Suman.hassan95 version.

function getWpCat($masterCategoryId = 3360, $returnCategories)
{
    
    $taxonomy     = 'product_cat';
    $orderby      = 'name';
    $show_count   = 1;      // 1 for yes, 0 for no
    $pad_counts   = 1;      // 1 for yes, 0 for no
    $hierarchical = 1;      // 1 for yes, 0 for no  
    $title        = '';
    $empty        = 0;

    $args = array(
        'taxonomy'     => $taxonomy,
        'orderby'      => $orderby,
        'show_count'   => $show_count,
        'pad_counts'   => $pad_counts,
        'hierarchical' => $hierarchical,
        'title_li'     => $title,
        'hide_empty'   => $empty,
        'parent' => $masterCategoryId
    );

    $all_categories = get_categories($args);
    
    foreach ($all_categories as $cat) {
        $returnCategories[$cat->slug] = $cat;
        $child = get_categories(array(
            'taxonomy'     => $taxonomy,
            'orderby'      => $orderby,
            'show_count'   => $show_count,
            'pad_counts'   => $pad_counts,
            'hierarchical' => $hierarchical,
            'title_li'     => $title,
            'hide_empty'   => $empty,
            'parent' => $cat->cat_ID
        ));
        if ( $child ) {
            $returnCategories[$cat->slug]->child =  getWpCat($cat->cat_ID, $returnCategories[$cat->slug]->child);
        }
        
    }
    return $returnCategories;
}

$returnCategories = [];
$categories = getWpCat(3360, $returnCategories);
LifeInstructor
  • 1,622
  • 1
  • 20
  • 24
0

For better reading I suggest to use a class instead of a function, by the way with the following code you can manage unlimited subcategories:

    class WoocommerceCategoriesDropdown
    {
        const PRODUCT_CAT = 'product_cat';
        const NAME = 'name';
        const SHOW_COUNT = 0;
        const PAD_COUNTS = 0;
        const HIERARCHICAL = 1;
        const TITLE = '';
        const HIDE_EMPTY = 0;
        const INITIAL_LEVEL = 0;
        private $all_categories;
        private $currentCategory;
        private $level;
    
        public function __construct($currentCategory)
        {
            $this->level = self::INITIAL_LEVEL;
            $this->currentCategory = $currentCategory;
            $this->all_categories = get_categories($this->getRootCategoryQueryArgs());
        }
    
        public function render()
        {
            echo '<select class="category-list-dropdown">';
            foreach ($this->all_categories as $cat) {
                $this->level = 0;
                if ($cat->category_parent == 0) {
                    echo '<option ' . $this->getSelected($cat) . ' data-link="' . get_term_link($cat->slug, self::PRODUCT_CAT) . '">' . $cat->name . '</option>';
                    $this->renderSubCategories($cat);
                }
            }
            echo '</select>';
        }
    
        /**
         * @return array
         */
        private function getRootCategoryQueryArgs(): array
        {
            return [
                'taxonomy' => self::PRODUCT_CAT,
                'orderby' => self::NAME,
                'show_count' => self::SHOW_COUNT,
                'pad_counts' => self::PAD_COUNTS,
                'hierarchical' => self::HIERARCHICAL,
                'title_li' => self::TITLE,
                'hide_empty' => self::HIDE_EMPTY,
            ];
        }
    
        /**
         * @return array
         */
        private function getSubCategoryQueryArgs($categoryId): array
        {
            $args2 = $this->getRootCategoryQueryArgs();
            $args2['child_of'] = $categoryId;
            $args2['parent'] = $categoryId;
            return $args2;
        }
    
        /**
         * @param $cat
         */
        public function renderSubCategories($cat): void
        {
            $subCats = get_categories($this->getSubCategoryQueryArgs($cat->term_id));
            if (!empty($subCats)) {
                $this->level++;
                foreach ($subCats as $subCategory) {
                    echo '<option ' . $this->getSelected($subCategory) . ' data-link="' . get_term_link($subCategory->slug, self::PRODUCT_CAT) . '">' . $this->getCategoryLevelSpacer() . $subCategory->name . '</option>';
                    $this->renderSubCategories($subCategory);
                }
            }
        }
    
        /**
         * @param $cat
         * @return string
         */
        private function getSelected($cat): string
        {
            $selected = get_term_link($cat->slug, self::PRODUCT_CAT) === get_term_link($this->currentCategory->slug, self::PRODUCT_CAT) ? ' selected="selected" ' : '';
            return $selected;
        }
    
        private function getCategoryLevelSpacer(): string
        {
            $spacer = '';
            for ($i = 0; $i < $this->level; $i++) {
                $spacer .= "-";
            }
            if (!empty($spacer)) {
                $spacer = $spacer . " ";
            }
            return $spacer;
        }
    }
enter code here
0

Thanks subZero for the initial code ... I know this is pretty simple but it might just help someone with a quick copy paste!

This returns the categories and subs in a nice array to use for building a menu instead of just echoing it out.

function getCategories(){
    $category = [];
    $taxonomy     = 'product_cat';
    $orderby      = 'name';
    $show_count   = 0;      // 1 for yes, 0 for no
    $pad_counts   = 0;      // 1 for yes, 0 for no
    $hierarchical = 1;      // 1 for yes, 0 for no
    $title        = '';
    $empty        = 0;

    $args = array(
        'taxonomy'     => $taxonomy,
        'orderby'      => $orderby,
        'show_count'   => $show_count,
        'pad_counts'   => $pad_counts,
        'hierarchical' => $hierarchical,
        'title_li'     => $title,
        'hide_empty'   => $empty
    );
    $all_categories = get_categories( $args );
    foreach ($all_categories as $cat) {
        if($cat->category_parent == 0) {
            $category_id = $cat->term_id;
            //echo '<br /><a href="'. get_term_link($cat->slug, 'product_cat') .'">'. $cat->name .'</a>';
            $category[$category_id]['name'] = $cat->name;
            $category[$category_id]['slug'] = get_term_link($cat->slug, 'product_cat');

            $args2 = array(
                    'taxonomy'     => $taxonomy,
                    'child_of'     => 0,
                    'parent'       => $category_id,
                    'orderby'      => $orderby,
                    'show_count'   => $show_count,
                    'pad_counts'   => $pad_counts,
                    'hierarchical' => $hierarchical,
                    'title_li'     => $title,
                    'hide_empty'   => $empty
            );
            $sub_cats = get_categories( $args2 );
            if($sub_cats) {
                foreach($sub_cats as $sub_category) {
                    //echo $sub_category->name;
                    $sub_category_id = $sub_category->term_id;
                    $category[$category_id]['sub_category'][$sub_category_id]['name'] = $sub_category->name;
                    $category[$category_id]['sub_category'][$sub_category_id]['slug'] = get_term_link($sub_category->slug, 'product_cat');
                }
            }
        }
    }
    return $category;
}
Mike Wells
  • 414
  • 4
  • 14