Automatically add WooCommerce product subcategories to categories in menu

add_filter( 'wp_get_nav_menu_items', 'wpcb_add_product_subcats_to_menu' );

if ( ! function_exists( 'wpcb_add_product_subcats_to_menu' ) ) {
	/**
	 * wpcb_add_product_subcats_to_menu.
	 *
	 * @see https://wpcodebook.com/snippets/automatically-add-woocommerce-product-subcategories-to-categories-in-menu/
	 */
	function wpcb_add_product_subcats_to_menu( $items ) {
		if ( is_admin() ) {
			return $items;
		}
		$children_order = count( $items ) + 1;
		foreach ( $items as $item ) {
			$items = wpcb_process_menu_item( $item, $items, $children_order );
		}
		return $items;
	}
}

if ( ! function_exists( 'wpcb_process_menu_item' ) ) {
	/**
	 * wpcb_process_menu_item.
	 */
	function wpcb_process_menu_item( $item, $items, &$children_order ) {
		if ( 'product_cat' === $item->object ) {

			$children = get_terms( array(
				'taxonomy'     => 'product_cat',
				'hide_empty'   => false,
				'parent'       => $item->object_id,
				'limit'        => -1,
				'orderby'      => 'title',
				'order'        => 'ASC',
			) );

			if ( ! empty( $children ) && ! is_wp_error( $children ) ) {
				foreach ( $children as $child ) {

					$tmp_item = new stdClass();

					$tmp_item->ID                      = 100000 + $children_order;
					$tmp_item->post_author             = $item->post_author;
					$tmp_item->post_date               = '';
					$tmp_item->post_date_gmt           = '';
					$tmp_item->post_content            = '';
					$tmp_item->post_title              = '';
					$tmp_item->post_excerpt            = '';
					$tmp_item->post_status             = 'publish';
					$tmp_item->comment_status          = '';
					$tmp_item->ping_status             = '';
					$tmp_item->post_password           = '';
					$tmp_item->post_name               = $child->name;
					$tmp_item->to_ping                 = '';
					$tmp_item->pinged                  = '';
					$tmp_item->post_modified           = '';
					$tmp_item->post_modified_gmt       = '';
					$tmp_item->post_content_filtered   = '';
					$tmp_item->post_parent             = 0;
					$tmp_item->guid                    = '';
					$tmp_item->menu_order              = $children_order;
					$tmp_item->post_type               = 'nav_menu_item';
					$tmp_item->post_mime_type          = '';
					$tmp_item->comment_count           = '';
					$tmp_item->filter                  = 'raw';
					$tmp_item->db_id                   = $tmp_item->ID;
					$tmp_item->menu_item_parent        = $item->ID;
					$tmp_item->object_id               = $child->term_id;
					$tmp_item->object                  = 'product_cat';
					$tmp_item->type                    = 'taxonomy';
					$tmp_item->type_label              = '';
					$tmp_item->url                     = get_term_link( $child->term_id );
					$tmp_item->title                   = $child->name;
					$tmp_item->target                  = '';
					$tmp_item->attr_title              = '';
					$tmp_item->description             = '';
					$tmp_item->classes                 = array( 0 => '' );
					$tmp_item->xfn                     = '';

					$items[] = $tmp_item;
					$children_order++;

					$items = wpcb_process_menu_item( $tmp_item, $items, $children_order );

				}
			}

		}
		return $items;
	}
}

5 Comments

  1. Nadia July 13, 2022 at 11:37 am #

    Thanks for the snippet.
    But how could it be to have the full categories tree and not just the first level?

    • Tom July 14, 2022 at 2:28 am #

      Hi Nadia,

      Happy to help 🙂

      I’ve just updated the snippet for you – now it will include the full categories tree.

      Please give it a try and let me know what you think.

    • Nadia July 18, 2022 at 1:58 pm #

      Thanks a lot! But it is not working as expected.

      With the previous snipped I had several menu items (shop and not), In one of that I had Man And Woman as shop categories and the snipped populated the links UNDER Man and Women with the first level categories.

      Now as a test I created a custom link “test”, inside it I placed the shop category “woman” and I expected to see something like
      – menu link 1
      – menu link 2
      – test
      – – woman
      – – – t-shirt
      – – – sweaters
      and so on.
      While I had directly all the sub categories, with no nidification, in the first level of the menu (deleting the word “test”). Moreover, I applied a CSS to “test” to hide it in test mode, but the class was missing.

      The result is:
      – menu link 1
      – menu link 2
      – t-shirt
      – sweaters

      Thanks

    • Tom July 19, 2022 at 12:01 am #

      Hi Nadia,

      I wasn’t able to reproduce the issue as you’ve described, however, I did find a bug when a category is displayed in the menu several times. Fixed it by replacing:

      $tmp_item->ID = $child->term_id * 10000;

      with:

      $tmp_item->ID = 100000 + $children_order;

      I’ve updated the snippet. Please give it a try and let me know if that helped.

      Regarding “… the class was missing…” – not sure if I got it correctly, but you can add a class to your menu items by replacing:

      $tmp_item->classes = array( 0 => '' );

      with:

      $tmp_item->classes = array( 'myclass1', 'myclass2' );

      Hope this helps. Please let me know if you have any questions.

  2. Nadia January 21, 2023 at 5:03 pm #

    sorry, haven’t been back.
    Thanks with your update it works fine but there is still an issue:
    if I have this menu (product categories are under the first menu voice “abbigliamento”
    https://snipboard.io/oTXU02.jpg
    the last two items doesn’t exist in front end (no in the html code),
    while if i switch the menu items in this way (not collapsed version, but I only switched the position of the last three items):
    https://snipboard.io/6t9gvs.jpg
    it works but not logged users don’t see the last item in the submenu “world”.

    If it may help, I have several voices disabled for logged in users and other for not logged in users.
    and the last one, in submenu “world” is activated only for not logged users (but it doesn’t work).

    If I disable your snippet, all works fine.

    Thanks!
    Nadia

Leave a Reply