WooCommerce Categories Menu with Images

Adding categories with images menu widget to head of site

screenshot

(Do not take it hard from visual point of view, it is WIP)

To make such things you need:

Register widget

It is almost straigh forward copy paste from WC_Widget_Product_Categories unfortunatelly there is no way to extend it

<?php

class Trofi_Widget_Product_Categories_Menu extends WC_Widget
{

    public $cat_ancestors;

    public $current_cat;

    public function __construct()
    {
        $this->widget_cssclass = 'trofi cat-menu';
        $this->widget_description = __('A list of product categories.', 'trofi');
        $this->widget_id = 'trofi_product_categories';
        $this->widget_name = __('Product Categories Menu', 'trofi');
        $this->settings = array(
            'title' => array(
                'type' => 'text',
                'std' => __('Product Categories', 'woocommerce'),
                'label' => __('Title', 'woocommerce')
            ),
            'orderby' => array(
                'type' => 'select',
                'std' => 'name',
                'label' => __('Order by', 'woocommerce'),
                'options' => array(
                    'order' => __('Category Order', 'woocommerce'),
                    'name' => __('Name', 'woocommerce')
                )
            )
        );

        parent::__construct();
    }

    public function widget($args, $instance)
    {
        global $wp_query, $post;

        $orderBy = isset($instance['orderby']) ? $instance['orderby'] : $this->settings['orderby']['std'];

        require_once(trailingslashit(get_stylesheet_directory()) . 'walkers/Trofi_Product_Cat_Menu_Walker.php');

        $list_args = [
            'show_count' => false,
            'hierarchical' => true,
            'taxonomy' => 'product_cat',
            'hide_empty' => true,
            'menu_order' => false,
            'title_li' => '',
            'pad_counts' => 1,
            'show_option_none' => __('No product categories exist.', 'woocommerce'),
            'walker' => new Trofi_Product_Cat_Menu_Walker
        ];

        // Menu Order
        if ($orderBy == 'order') {
            $list_args['menu_order'] = 'asc';
        } else {
            $list_args['orderby'] = 'title';
        }

        // Setup Current Category
        $this->current_cat = false;
        $this->cat_ancestors = array();

        if (is_tax('product_cat')) {
            $this->current_cat = $wp_query->queried_object;
            $this->cat_ancestors = get_ancestors($this->current_cat->term_id, 'product_cat');
        } elseif (is_singular('product')) {
            $product_category = wc_get_product_terms($post->ID, 'product_cat', array('orderby' => 'parent'));

            if ($product_category) {
                $this->current_cat = end($product_category);
                $this->cat_ancestors = get_ancestors($this->current_cat->term_id, 'product_cat');
            }
        }
        $list_args['current_category'] = ($this->current_cat) ? $this->current_cat->term_id : '';
        $list_args['current_category_ancestors'] = $this->cat_ancestors;


        $this->widget_start($args, $instance);
        echo '<ul class="product-categories">';
        wp_list_categories(apply_filters('woocommerce_product_categories_widget_args', $list_args));
        echo '</ul>';
        $this->widget_end($args);
    }
}

in your functions.php:

add_action('widgets_init', function () {
    require_once(trailingslashit(get_stylesheet_directory()) . 'widgets/Trofi_Widget_Product_Categories_Menu.php');
    register_widget('Trofi_Widget_Product_Categories_Menu');
});

The key difference here is that we use our custom walker Trofi_Product_Cat_Menu_Walker which will render category image for top level categories.

Walker

Here we are extending WooCommerce product categories list walker and overriding its start_el method. The only difference from parent method if that we are adding category images inside anchors for top level categories.

<?php
if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}

require_once(ABSPATH . '/wp-content/plugins/woocommerce/includes/walkers/class-product-cat-list-walker.php');

class Trofi_Product_Cat_Menu_Walker extends WC_Product_Cat_List_Walker
{
    public function start_el(&$output, $cat, $depth = 0, $args = array(), $current_object_id = 0)
    {
        $output .= '<li class="cat-item cat-item-' . $cat->term_id;

        if ($args['current_category'] == $cat->term_id) {
            $output .= ' current-cat';
        }

        if ($args['has_children'] && $args['hierarchical']) {
            $output .= ' cat-parent';
        }

        if ($args['current_category_ancestors'] && $args['current_category'] && in_array($cat->term_id, $args['current_category_ancestors'])) {
            $output .= ' current-cat-parent';
        }

        //$output .=  '"><a href="' . get_term_link( (int) $cat->term_id, 'product_cat' ) . '">' . __($cat->name, 'woocommerce') . '</a>';
        $output .= '"><a href="' . get_term_link((int)$cat->term_id, 'product_cat') . '">';
        if ($depth === 0) {
            $category_thumbnail = get_woocommerce_term_meta($cat->term_id, 'thumbnail_id', true);
            $image = wp_get_attachment_url($category_thumbnail);
            $image = $image ? $image : get_theme_mod('trofi_category_default_image');
            $image = $image ? $image : 'http://placehold.it/100x100&text=No+photo';
            $output .= '<span class="category-image" style="background-image:url(' . esc_attr($image) . ')"></span>';
        }
        $output .= __($cat->name, 'woocommerce') . '</a>';
    }
}

And thats all, now you can drop widget to header area or call it from functions.php

add_action('init', function () {
    add_action('storefront_header', function () {
        the_widget('Trofi_Widget_Product_Categories_Menu');
    }, 60);
});

All is left is to have fun with styling it :)